From 48db56372b3b8ff698ca327afce4917e442099e9 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Mon, 24 Jun 2024 02:35:13 -0400 Subject: [PATCH 1/4] docs: add warning about `isObject` and `Object.create(null)` --- docs/typed/is-object.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/typed/is-object.mdx b/docs/typed/is-object.mdx index 0e746501..1df85ea1 100644 --- a/docs/typed/is-object.mdx +++ b/docs/typed/is-object.mdx @@ -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)`. From 6a74046abb6e8460bd9f48ababe409421bce17f2 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Mon, 24 Jun 2024 02:36:09 -0400 Subject: [PATCH 2/4] fix: use isPlainObject internally --- src/object/assign.ts | 4 ++-- src/object/keys.ts | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/object/assign.ts b/src/object/assign.ts index 40b7d779..ad79bbf2 100644 --- a/src/object/assign.ts +++ b/src/object/assign.ts @@ -1,4 +1,4 @@ -import { isObject } from 'radashi' +import { isPlainObject } from 'radashi' /** * Merges two objects together recursivly into a new object applying @@ -13,7 +13,7 @@ export const assign = >( const merged = { ...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] } diff --git a/src/object/keys.ts b/src/object/keys.ts index b0791da3..d624179b 100644 --- a/src/object/keys.ts +++ b/src/object/keys.ts @@ -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). @@ -11,7 +10,7 @@ import { isObject } from 'radashi' export const keys = (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]) ) From dabbaaabac5fe6965c4d25fb15c4b71e2cfafc1b Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Mon, 24 Jun 2024 03:06:12 -0400 Subject: [PATCH 3/4] test: assign/keys with Object.create(null) --- src/object/tests/assign.test.ts | 9 +++++++++ src/object/tests/keys.test.ts | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/object/tests/assign.test.ts b/src/object/tests/assign.test.ts index 4f7bcb76..185b623d 100644 --- a/src/object/tests/assign.test.ts +++ b/src/object/tests/assign.test.ts @@ -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) + }) }) diff --git a/src/object/tests/keys.test.ts b/src/object/tests/keys.test.ts index 1168cedc..68a5d0c6 100644 --- a/src/object/tests/keys.test.ts +++ b/src/object/tests/keys.test.ts @@ -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']) + }) }) From 9dc1b703c483b1216e43c954d8a6b52644993153 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:00:33 -0400 Subject: [PATCH 4/4] fix: preserve null prototype in assign result --- src/object/assign.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/object/assign.ts b/src/object/assign.ts index ad79bbf2..95218bae 100644 --- a/src/object/assign.ts +++ b/src/object/assign.ts @@ -10,7 +10,10 @@ export const assign = >( 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] = isPlainObject(initial[key])