Skip to content

Commit

Permalink
feat(guard): add isPlainObject
Browse files Browse the repository at this point in the history
  • Loading branch information
ASafaeirad committed Jan 19, 2024
1 parent 23117ee commit 7fffc14
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 43 deletions.
1 change: 1 addition & 0 deletions docs/pages/guard/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"is-not-null": "isNotNull",
"is-null": "isNull",
"is-object": "isObject",
"is-plain-object": "isPlainObject",
"is-promise": "isPromise",
"is-set": "isSet",
"is-string": "isString",
Expand Down
36 changes: 18 additions & 18 deletions docs/pages/guard/is-object.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# isObject

Check whether the given value is an object
Check whether the given value is an object.

### Import

Expand All @@ -17,21 +17,21 @@ function isObject(x: unknown): x is ObjectType;
### Examples

```typescript copy
isObject('') // false
isObject('hello world') // false
isObject(null) // false
isObject(true) // false
isObject(undefined) // false
isObject(NaN) // false
isObject(0) // false
isObject(isObject) // false
isObject(false) // false
isObject([]) // false
isObject([2]) // false
isObject(new Map()) // false
isObject(new Set()) // false
isObject(new RegExp('foo')) // false
isObject({}) // true
isObject({ a: 2 }) // true
isObject({ 2: 'a' }) // true
isObject('') // false
isObject('hello world') // false
isObject(null) // false
isObject(true) // false
isObject(undefined) // false
isObject(NaN) // false
isObject(0) // false
isObject(isObject) // false
isObject(false) // false
isObject([]) // false
isObject([2]) // false
isObject(new Map()) // true
isObject(new Set()) // true
isObject(new RegExp('foo')) // true
isObject({}) // true
isObject({ a: 2 }) // true
isObject({ 2: 'a' }) // true
```
37 changes: 37 additions & 0 deletions docs/pages/guard/is-plain-object.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# isPlainObject

Chec whether the given value was created by the Object constructor, or `Object.create(null)`.

### Import

```typescript copy
import { isPlainObject } from '@fullstacksjs/toolbox';
```

### Signature

```typescript copy
function isPlainObject(x: unknown): x is ObjectType;
```

### Examples

```typescript copy
isPlainObject('') // false
isPlainObject('hello world') // false
isPlainObject(null) // false
isPlainObject(true) // false
isPlainObject(undefined) // false
isPlainObject(NaN) // false
isPlainObject(0) // false
isPlainObject(isPlainObject) // false
isPlainObject(false) // false
isPlainObject([]) // false
isPlainObject([2]) // false
isPlainObject(new Map()) // false
isPlainObject(new Set()) // false
isPlainObject(new RegExp('foo')) // false
isPlainObject({}) // true
isPlainObject({ a: 2 }) // true
isPlainObject({ 2: 'a' }) // true
```
6 changes: 3 additions & 3 deletions src/guards/isObject.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ describe('isObject', () => {
{ x: false, expected: false },
{ x: [], expected: false },
{ x: [2], expected: false },
{ x: new Map(), expected: false },
{ x: new Set(), expected: false },
{ x: new RegExp('foo'), expected: false },
{ x: new Map(), expected: true },
{ x: new Set(), expected: true },
{ x: new RegExp('foo'), expected: true },
{ x: {}, expected: true },
{ x: { a: 2 }, expected: true },
{ x: { 2: 'a' }, expected: true },
Expand Down
39 changes: 19 additions & 20 deletions src/guards/isObject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { ObjectType } from '../types/types';
import { getTypeOf } from '../types';
import type { ObjectType } from '../types';

/**
* Check whether the given value is an object
Expand All @@ -9,23 +8,23 @@ import { getTypeOf } from '../types';
*
* @example
*
* isObject('') // false
* isObject('hello world') // false
* isObject(null) // false
* isObject(true) // false
* isObject(undefined) // false
* isObject(NaN) // false
* isObject(0) // false
* isObject(isObject) // false
* isObject(false) // false
* isObject([]) // false
* isObject([2]) // false
* isObject(new Map()) // false
* isObject(new Set()) // false
* isObject(new RegExp('foo')) // false
* isObject({}) // true
* isObject({ a: 2 }) // true
* isObject({ 2: 'a' }) // true
* isObject('') // false
* isObject('hello world') // false
* isObject(null) // false
* isObject(true) // false
* isObject(undefined) // false
* isObject(NaN) // false
* isObject(0) // false
* isObject(isObject) // false
* isObject(false) // false
* isObject([]) // false
* isObject([2]) // false
* isObject(new Map()) // false
* isObject(new Set()) // false
* isObject(new RegExp('foo')) // false
* isObject({}) // true
* isObject({ a: 2 }) // true
* isObject({ 2: 'a' }) // true
*/
export const isObject = (x: unknown): x is ObjectType =>
getTypeOf(x) === 'object' && x !== null;
typeof x === 'object' && !Array.isArray(x) && x !== null;
30 changes: 30 additions & 0 deletions src/guards/isPlainObject.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { isPlainObject } from './isPlainObject.ts';

describe('isPlainObject', () => {
const cases = [
{ x: '', expected: false },
{ x: 'hello world', expected: false },
{ x: null, expected: false },
{ x: true, expected: false },
{ x: undefined, expected: false },
{ x: NaN, expected: false },
{ x: 0, expected: false },
{ x: isPlainObject, expected: false },
{ x: false, expected: false },
{ x: [], expected: false },
{ x: [2], expected: false },
{ x: new Map(), expected: false },
{ x: new Set(), expected: false },
{ x: new RegExp('foo'), expected: false },
{ x: {}, expected: true },
{ x: { a: 2 }, expected: true },
{ x: { 2: 'a' }, expected: true },
];

it.each(cases)(
'should return $expected for $x as input',
({ x, expected }) => {
expect(isPlainObject(x)).toBe(expected);
},
);
});
22 changes: 22 additions & 0 deletions src/guards/isPlainObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { ObjectType } from '../types/types';

function isObject(o: unknown): o is ObjectType {
return Object.prototype.toString.call(o) === '[object Object]';
}

export function isPlainObject(o: unknown): o is ObjectType {
if (!isObject(o)) return false;

const ctor = o.constructor;

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ctor == null) return true;

const prototype = ctor.prototype;
if (!isObject(prototype)) return false;

// If constructor does not have an Object-specific method
if (!prototype.hasOwnProperty('isPrototypeOf')) return false;

return true;
}
5 changes: 3 additions & 2 deletions src/object/merge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isObject, isMap, isSet } from '../guards';
import { isMap, isSet } from '../guards';
import { isPlainObject } from '../guards/isPlainObject';
import type { Merge, ObjectType } from '../types/types';

interface ComposerArguments {
Expand All @@ -19,7 +20,7 @@ function defaultComposer({
path,
extract,
}: ComposerArguments): unknown {
if (isObject(v1) && isObject(v2)) return extract(v1, v2, path);
if (isPlainObject(v1) && isPlainObject(v2)) return extract(v1, v2, path);
else if (Array.isArray(v1) && Array.isArray(v2)) return [...v1, ...v2];
else if (isSet(v1) && isSet(v2)) return new Set([...v1, ...v2]);
else if (isMap(v1) && isMap(v2)) return new Map([...v1, ...v2]);
Expand Down

0 comments on commit 7fffc14

Please sign in to comment.