Skip to content

Commit

Permalink
feat: _omitWithUndefined, _pickWithUndefined
Browse files Browse the repository at this point in the history
Faster alternatives to _omit/_pick
  • Loading branch information
kirillgroshkov committed Aug 6, 2024
1 parent 1837b89 commit 37bc8da
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 32 deletions.
93 changes: 90 additions & 3 deletions src/object/object.util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
_objectAssignExact,
_objectNullValuesToUndefined,
_omit,
_omitWithUndefined,
_pick,
_pickWithUndefined,
_set,
_unset,
} from './object.util'
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -68,8 +106,6 @@ test('_omit', () => {

_deepFreeze(obj)

// expect(_omit(obj)).toEqual(obj)

// empty props
expect(_omit(obj, [])).toEqual(obj)

Expand All @@ -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,
Expand All @@ -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',
Expand Down
54 changes: 44 additions & 10 deletions src/object/object.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,33 @@ export function _pick<T extends AnyObject, K extends keyof T>(
): 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<T extends AnyObject, K extends keyof T>(
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
}
Expand All @@ -44,15 +62,31 @@ export function _omit<T extends AnyObject, K extends keyof T>(
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<T extends AnyObject, K extends keyof T>(
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
}
Expand All @@ -67,8 +101,8 @@ export function _omit<T extends AnyObject, K extends keyof T>(
*/
export function _mask<T extends AnyObject>(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
}
Expand Down
16 changes: 9 additions & 7 deletions src/object/sortObject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { AnyObject } from '../index'
import { _omit } from '../index'

/**
* Returns new object with keys sorder in the given order.
Expand All @@ -9,15 +8,18 @@ import { _omit } from '../index'
export function _sortObject<T extends AnyObject>(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
}
18 changes: 6 additions & 12 deletions src/object/sortObjectDeep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { _isObject } from '..'

/**
* based on: https://github.com/IndigoUnited/js-deep-sort-object
*/
Expand All @@ -9,16 +7,12 @@ export function _sortObjectDeep<T>(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
Expand Down

0 comments on commit 37bc8da

Please sign in to comment.