diff --git a/src/object/object.util.test.ts b/src/object/object.util.test.ts index 126cbe6c..b16efd96 100644 --- a/src/object/object.util.test.ts +++ b/src/object/object.util.test.ts @@ -22,7 +22,9 @@ import { _objectAssignExact, _objectNullValuesToUndefined, _omit, + _omitWithUndefined, _pick, + _pickWithUndefined, _set, _unset, } from './object.util' @@ -42,7 +44,6 @@ test('_pick', () => { const fields = ['a', 'c', 'd', 'e'] as const const r = f(obj, fields as any) - // console.log(r) expect(r).toEqual({ a: 1, c: 3, d: false }) expect('e' in r).toBe(false) // should not add more fields with 'undefined' value // should not mutate @@ -57,6 +58,43 @@ test('_pick', () => { expect(obj2).toBe(obj) }) +test('_pickWithUndefined', () => { + const f = _pickWithUndefined + + const obj = { + a: 1, + b: 2, + c: 3, + d: false, + } + + // empty fields + expect(f(obj, [])).toEqual({ + a: undefined, + b: undefined, + c: undefined, + d: undefined, + }) + + const fields = ['a', 'c', 'd', 'e'] as const + const r = f(obj, fields as any) + expect(r).toEqual({ a: 1, b: undefined, c: 3, d: false }) + expect('e' in r).toBe(false) // should not add more fields with 'undefined' value + // should not mutate + expect(obj.c).toBe(3) + expect(r).not.toBe(obj) + + // should mutate + const obj2 = f(obj, ['a'], true) + expect(obj2).toEqual({ + a: 1, + b: undefined, + c: undefined, + d: undefined, + }) + expect(obj2).toBe(obj) +}) + test('_omit', () => { const obj = { a: 1, @@ -68,8 +106,6 @@ test('_omit', () => { _deepFreeze(obj) - // expect(_omit(obj)).toEqual(obj) - // empty props expect(_omit(obj, [])).toEqual(obj) @@ -85,6 +121,37 @@ test('_omit', () => { }) }) +test('_omitWithUndefined', () => { + const obj = { + a: 1, + b: 2, + c: 3, + d: false, + e: undefined, + } + + _deepFreeze(obj) + + // empty props + expect(_omitWithUndefined(obj, [])).toEqual(obj) + + expect(_omitWithUndefined(obj, ['b', 'c'])).toEqual({ + a: 1, + b: undefined, + c: undefined, + d: false, + e: undefined, + }) + + expect(_omit(obj, ['a', 'd', 'e'])).toEqual({ + a: undefined, + b: 2, + c: 3, + d: undefined, + e: undefined, + }) +}) + test('_omit mutate', () => { const obj = { a: 1, @@ -103,6 +170,26 @@ test('_omit mutate', () => { expect(obj2).toBe(obj) }) +test('_omitWithUndefined mutating', () => { + const obj = { + a: 1, + b: 2, + c: 3, + d: false, + e: undefined, + } + + const obj2 = _omitWithUndefined(obj, ['b', 'c'], true) + expect(obj2).toEqual({ + a: 1, + b: undefined, + c: undefined, + d: false, + e: undefined, + }) + expect(obj2).toBe(obj) +}) + test('_mask', () => { const o = { a: '1', diff --git a/src/object/object.util.ts b/src/object/object.util.ts index 2d64f48f..73879890 100644 --- a/src/object/object.util.ts +++ b/src/object/object.util.ts @@ -21,15 +21,33 @@ export function _pick( ): T { if (mutate) { // Start as original object (mutable), DELETE properties that are not whitelisted - for (const prop of Object.keys(obj)) { - if (!props.includes(prop as K)) delete obj[prop] + for (const k of Object.keys(obj)) { + if (!props.includes(k as K)) delete obj[k] } return obj } // Start as empty object, pick/add needed properties const r = {} as T - for (const prop of props) { - if (prop in obj) r[prop] = obj[prop] + for (const k of props) { + if (k in obj) r[k] = obj[k] + } + return r +} + +/** + * Sets all properties of an object except passed ones to `undefined`. + * This is a more performant alternative to `_pick` that does picking/deleting. + */ +export function _pickWithUndefined( + obj: T, + props: readonly K[], + mutate = false, +): T { + const r: T = mutate ? obj : { ...obj } + for (const k of Object.keys(r)) { + if (!props.includes(k as K)) { + r[k as K] = undefined as any + } } return r } @@ -44,15 +62,31 @@ export function _omit( mutate = false, ): T { if (mutate) { - for (const prop of props) { - delete obj[prop] + for (const k of props) { + delete obj[k] } return obj } const r = {} as T - for (const prop of Object.keys(obj)) { - if (!props.includes(prop as K)) r[prop as K] = obj[prop] + for (const k of Object.keys(obj)) { + if (!props.includes(k as K)) r[k as K] = obj[k] + } + return r +} + +/** + * Sets all passed properties of an object to `undefined`. + * This is a more performant alternative to `_omit` that does picking/deleting. + */ +export function _omitWithUndefined( + obj: T, + props: readonly K[], + mutate = false, +): T { + const r: T = mutate ? obj : { ...obj } + for (const k of props) { + r[k] = undefined as any } return r } @@ -67,8 +101,8 @@ export function _omit( */ export function _mask(obj: T, props: string[], mutate = false): T { const r = mutate ? obj : _deepCopy(obj) - for (const prop of props) { - _unset(r, prop) + for (const k of props) { + _unset(r, k) } return r } diff --git a/src/object/sortObject.ts b/src/object/sortObject.ts index f3a7cbb6..06c4dde7 100644 --- a/src/object/sortObject.ts +++ b/src/object/sortObject.ts @@ -1,5 +1,4 @@ import type { AnyObject } from '../index' -import { _omit } from '../index' /** * Returns new object with keys sorder in the given order. @@ -9,15 +8,18 @@ import { _omit } from '../index' export function _sortObject(obj: T, keyOrder: (keyof T)[]): T { const r = {} as T - keyOrder.forEach(key => { - if (key in obj) { - r[key] = obj[key] + // First, go over ordered keys + for (const k of keyOrder) { + if (k in obj) { + r[k] = obj[k] } - }) + } - Object.entries(_omit(obj, keyOrder)).forEach(([k, v]) => { + // Second, go over all other keys + for (const [k, v] of Object.entries(obj)) { + if (keyOrder.includes(k)) continue r[k as keyof T] = v - }) + } return r } diff --git a/src/object/sortObjectDeep.ts b/src/object/sortObjectDeep.ts index 13cb7021..f7eeea79 100644 --- a/src/object/sortObjectDeep.ts +++ b/src/object/sortObjectDeep.ts @@ -1,5 +1,3 @@ -import { _isObject } from '..' - /** * based on: https://github.com/IndigoUnited/js-deep-sort-object */ @@ -9,16 +7,12 @@ export function _sortObjectDeep(o: T): T { return o.map(_sortObjectDeep) as any } - if (_isObject(o)) { - const out = {} as T - - Object.keys(o) - .sort((a, b) => a.localeCompare(b)) - .forEach(k => { - out[k as keyof T] = _sortObjectDeep(o[k as keyof T]) - }) - - return out + if (o && typeof o === 'object') { + const r = {} as T + for (const k of Object.keys(o).sort((a, b) => a.localeCompare(b)) as (keyof T)[]) { + r[k] = _sortObjectDeep(o[k]) + } + return r } return o