Skip to content

Commit

Permalink
fix: improve pick and omit
Browse files Browse the repository at this point in the history
to support any number of keys
  • Loading branch information
unional committed Oct 13, 2022
1 parent 009363f commit b7628cb
Showing 4 changed files with 83 additions and 59 deletions.
29 changes: 23 additions & 6 deletions ts/object/omit.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assertType, Equal, Except, Omit, omit } from '../index.js'
import { assertType, Equal, Except, isType, Omit, omit } from '../index.js'

describe('Omit', () => {
describe('Omit<T, K>', () => {
test('work with primitive types', () => {
type N = Omit<number, 'toFixed'>
assertType.isFunction((() => ({})) as N['toExponential'])
@@ -68,18 +68,35 @@ describe('Omit', () => {
}
foo({ b: '1' })
})
})

test('omit properties from object', () => {
describe(`${omit.name}()`, () => {
it('omits properties from object', () => {
const actual = omit({ a: 1, b: 2 }, 'a')

expect(actual).toEqual({ b: 2 })
isType.equal<true, 'b', keyof typeof actual>()
})

test('omit all', () => {
const actual = omit({ a: 1 }, 'a')
it('returns a empty object type when all props are omitted', () => {
const actual = omit({ a: 1, b: 1 }, 'a', 'b')

expect(actual).toEqual({})
assertType.isTrue(true as Equal<keyof typeof actual, never>)
isType.equal<true, never, keyof typeof actual>()
})

it('can object from generic record', () => {
const i: Record<string, any> = { a: 1, b: 2 }
const r = omit(i, 'a')
expect(r).toEqual({ b: 2 })
isType.equal<true, Record<string, any>, typeof r>()
})

it('more than 12', () => {
const actual = omit({ a: 1, b: 1, c: 1 }, 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'b')

expect(actual).toEqual({ c: 1 })
assertType.isTrue(true as Equal<keyof typeof actual, 'c'>)
})
})

5 changes: 3 additions & 2 deletions ts/object/omit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnyRecord, reduceByKey } from '../object/index.js'
import { AnyRecord, record, reduceByKey } from '../object/index.js'
import { UnionKeys } from '../UnionKeys.js'
import { Pick } from './pick.js'

@@ -28,9 +28,10 @@ export function omit<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends Un
export function omit<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends UnionKeys<T>, P3 extends UnionKeys<T>, P4 extends UnionKeys<T>, P5 extends UnionKeys<T>, P6 extends UnionKeys<T>, P7 extends UnionKeys<T>, P8 extends UnionKeys<T>, P9 extends UnionKeys<T>, P10 extends UnionKeys<T>>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10): Omit<T, P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10>
export function omit<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends UnionKeys<T>, P3 extends UnionKeys<T>, P4 extends UnionKeys<T>, P5 extends UnionKeys<T>, P6 extends UnionKeys<T>, P7 extends UnionKeys<T>, P8 extends UnionKeys<T>, P9 extends UnionKeys<T>, P10 extends UnionKeys<T>, P11 extends UnionKeys<T>>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10, prop11: P11): Omit<T, P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10 | P11>
export function omit<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends UnionKeys<T>, P3 extends UnionKeys<T>, P4 extends UnionKeys<T>, P5 extends UnionKeys<T>, P6 extends UnionKeys<T>, P7 extends UnionKeys<T>, P8 extends UnionKeys<T>, P9 extends UnionKeys<T>, P10 extends UnionKeys<T>, P11 extends UnionKeys<T>, P12 extends UnionKeys<T>>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10, prop11: P11, prop12: P12): Omit<T, P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10 | P11 | P12>
export function omit<T extends AnyRecord, Props extends UnionKeys<T>>(subject: T, ...props: Props[]): Omit<T, Props>
export function omit<T extends AnyRecord>(subject: T, ...props: Array<UnionKeys<T>>) {
return reduceByKey(subject, (p, k) => {
if (props.indexOf(k) === -1) p[k] = subject[k]
return p
}, {} as AnyRecord)
}, record())
}
104 changes: 54 additions & 50 deletions ts/object/pick.spec.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,70 @@
import { assertType, canAssign, Equal, Pick, pick } from '../index.js'

test('pick properties from object', () => {
const actual = pick({ a: 1, b: 2 }, 'a')
describe(`${pick.name}()`, () => {
it('picks properties from object', () => {
const actual = pick({ a: 1, b: 2 }, 'a')

expect(actual).toEqual({ a: 1 })
expect(actual).toEqual({ a: 1 })
})
})

test('distributive pick', () => {
type Action = InvokeAction | ReturnAction
describe(`Pick<T, K>`, () => {
test('distributive pick', () => {
type Action = InvokeAction | ReturnAction

type InvokeAction = {
type: 'invoke',
id: string,
payload: string[],
}
type InvokeAction = {
type: 'invoke',
id: string,
payload: string[],
}

type ReturnAction = {
type: 'return',
id: string,
payload: string,
}
type ReturnAction = {
type: 'return',
id: string,
payload: string,
}

const x: Pick<Action, 'type' | 'payload'> = { type: 'invoke', payload: [] }
const x: Pick<Action, 'type' | 'payload'> = { type: 'invoke', payload: [] }

const actions: Action[] = []
const actions: Action[] = []

actions.push({ ...x, id: '1' })
})
actions.push({ ...x, id: '1' })
})

test('distributive pick with disjoined keys', () => {
type Union = {
type: 'A',
foo: string,
} | {
type: 'B',
foo: string,
bar: string,
}
type Id<T> = { [P in keyof T]: T[P] }
let x: Id<Pick<Union, 'type' | 'bar'>> = { type: 'A' }
x = { type: 'B', bar: 'bar' }
test('distributive pick with disjoined keys', () => {
type Union = {
type: 'A',
foo: string,
} | {
type: 'B',
foo: string,
bar: string,
}
type Id<T> = { [P in keyof T]: T[P] }
let x: Id<Pick<Union, 'type' | 'bar'>> = { type: 'A' }
x = { type: 'B', bar: 'bar' }

expect(x.bar).toBe('bar')
})
expect(x.bar).toBe('bar')
})

test('intersection types with generic', () => {
type Foo = { a: string, b: string }
function foo<T>(input: Pick<Foo & T, 'a'>): void {
assertType.isString(input.a)
}
foo({ a: '1' })
})
test('intersection types with generic', () => {
type Foo = { a: string, b: string }
function foo<T>(input: Pick<Foo & T, 'a'>): void {
assertType.isString(input.a)
}
foo({ a: '1' })
})

test('optional property remains optional', () => {
type Foo = { a?: string, b: string }
type A = Pick<Foo, 'a'>
assertType.isTrue(canAssign<A>()({}))
})
test('optional property remains optional', () => {
type Foo = { a?: string, b: string }
type A = Pick<Foo, 'a'>
assertType.isTrue(canAssign<A>()({}))
})

test('pick never gets empty object', () => {
type A = { a: number }
type S = Pick<A, never>
type K = keyof S
assertType.isTrue(true as Equal<K, never>)
test('pick never gets empty object', () => {
type A = { a: number }
type S = Pick<A, never>
type K = keyof S
assertType.isTrue(true as Equal<K, never>)
})
})
4 changes: 3 additions & 1 deletion ts/object/pick.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { record } from 'type-plus'
import { UnionKeys } from '../UnionKeys.js'
import type { AnyRecord } from './AnyRecord.js'
import { reduceByKey } from './reduceKey.js'
@@ -14,11 +15,12 @@ export function pick<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends Un
export function pick<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends UnionKeys<T>, P3 extends UnionKeys<T>, P4 extends UnionKeys<T>, P5 extends UnionKeys<T>, P6 extends UnionKeys<T>, P7 extends UnionKeys<T>, P8 extends UnionKeys<T>, P9 extends UnionKeys<T>, P10 extends UnionKeys<T>>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10): Pick<T, P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10>
export function pick<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends UnionKeys<T>, P3 extends UnionKeys<T>, P4 extends UnionKeys<T>, P5 extends UnionKeys<T>, P6 extends UnionKeys<T>, P7 extends UnionKeys<T>, P8 extends UnionKeys<T>, P9 extends UnionKeys<T>, P10 extends UnionKeys<T>, P11 extends UnionKeys<T>>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10, prop11: P11): Pick<T, P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10 | P11>
export function pick<T extends AnyRecord, P1 extends UnionKeys<T>, P2 extends UnionKeys<T>, P3 extends UnionKeys<T>, P4 extends UnionKeys<T>, P5 extends UnionKeys<T>, P6 extends UnionKeys<T>, P7 extends UnionKeys<T>, P8 extends UnionKeys<T>, P9 extends UnionKeys<T>, P10 extends UnionKeys<T>, P11 extends UnionKeys<T>, P12 extends UnionKeys<T>>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10, prop11: P11, prop12: P12): Pick<T, P1 | P2 | P3 | P4 | P5 | P6 | P7 | P8 | P9 | P10 | P11 | P12>
export function pick<T extends AnyRecord, Props extends UnionKeys<T>>(subject: T, ...props: Props[]): Pick<T, Props>
export function pick<T extends AnyRecord>(subject: T, ...props: Array<UnionKeys<T>>) {
return reduceByKey(subject, (p, k) => {
if (props.indexOf(k) >= 0) p[k] = subject[k]
return p
}, {} as T)
}, record())
}

/**

0 comments on commit b7628cb

Please sign in to comment.