Skip to content

Commit

Permalink
fix: avoid isObject for internal use (#18)
Browse files Browse the repository at this point in the history
* docs: add warning about `isObject` and `Object.create(null)`

* fix: use isPlainObject internally

* test: assign/keys with Object.create(null)

* fix: preserve null prototype in assign result
  • Loading branch information
aleclarson authored Jun 25, 2024
1 parent b5469a6 commit 12d6fd0
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 6 deletions.
2 changes: 2 additions & 0 deletions docs/typed/is-object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ isObject(['hello']) // => false
isObject(null) // => false
isObject({ say: 'hello' }) // => true
```

**Beware:** This function returns `false` for objects created with `Object.create(null)`.
9 changes: 6 additions & 3 deletions src/object/assign.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject } from 'radashi'
import { isPlainObject } from 'radashi'

/**
* Merges two objects together recursivly into a new object applying
Expand All @@ -10,10 +10,13 @@ export const assign = <X extends Record<string | symbol | number, any>>(
override: X
): X => {
if (!initial || !override) return initial ?? override ?? {}
const merged = { ...initial }
const proto = Object.getPrototypeOf(initial)
const merged = proto
? { ...initial }
: Object.assign(Object.create(proto), initial)
for (const key in override) {
if (Object.prototype.hasOwnProperty.call(override, key)) {
merged[key] = isObject(initial[key])
merged[key] = isPlainObject(initial[key])
? assign(initial[key], override[key])
: override[key]
}
Expand Down
5 changes: 2 additions & 3 deletions src/object/keys.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { isArray } from 'radashi'
import { isObject } from 'radashi'
import { isArray, isPlainObject } from 'radashi'

/**
* Get a string list of all key names that exist in an object (deep).
Expand All @@ -11,7 +10,7 @@ import { isObject } from 'radashi'
export const keys = <TValue extends object>(value: TValue): string[] => {
if (!value) return []
const getKeys = (nested: any, paths: string[]): string[] => {
if (isObject(nested)) {
if (isPlainObject(nested)) {
return Object.entries(nested).flatMap(([k, v]) =>
getKeys(v, [...paths, k])
)
Expand Down
9 changes: 9 additions & 0 deletions src/object/tests/assign.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,13 @@ describe('assign function', () => {
const result = _.assign({}, { b: 'y' })
expect(result).toEqual({ b: 'y' })
})
test('works with Object.create(null)', () => {
const object = { a: Object.create(null) }
object.a.b = 1

const result = _.assign(object, { a: { c: 2 } })

expect(result).toEqual({ a: { b: 1, c: 2 } })
expect(Object.getPrototypeOf(result.a)).toBe(null)
})
})
8 changes: 8 additions & 0 deletions src/object/tests/keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,12 @@ describe('keys function', () => {
'enemies.0.power'
])
})
test('works with Object.create(null)', () => {
const object = Object.create(null)
object.a = 1
object.b = [2]
object.c = { d: 3 }
const result = _.keys(object)
expect(result).toEqual(['a', 'b.0', 'c.d'])
})
})

0 comments on commit 12d6fd0

Please sign in to comment.