From b7628cbb1b5c9b005abca7abea62eb761170dd1c Mon Sep 17 00:00:00 2001 From: Homa Wong Date: Wed, 12 Oct 2022 17:18:09 -0700 Subject: [PATCH] fix: improve pick and omit to support any number of keys --- ts/object/omit.spec.ts | 29 +++++++++--- ts/object/omit.ts | 5 +- ts/object/pick.spec.ts | 104 +++++++++++++++++++++-------------------- ts/object/pick.ts | 4 +- 4 files changed, 83 insertions(+), 59 deletions(-) diff --git a/ts/object/omit.spec.ts b/ts/object/omit.spec.ts index 3da3d356c2..45f90d3818 100644 --- a/ts/object/omit.spec.ts +++ b/ts/object/omit.spec.ts @@ -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', () => { test('work with primitive types', () => { type N = Omit 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() }) - 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) + isType.equal() + }) + + it('can object from generic record', () => { + const i: Record = { a: 1, b: 2 } + const r = omit(i, 'a') + expect(r).toEqual({ b: 2 }) + isType.equal, 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) }) }) diff --git a/ts/object/omit.ts b/ts/object/omit.ts index 081a779f0f..dce8fb8edb 100644 --- a/ts/object/omit.ts +++ b/ts/object/omit.ts @@ -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, P2 extends Un export function omit, P2 extends UnionKeys, P3 extends UnionKeys, P4 extends UnionKeys, P5 extends UnionKeys, P6 extends UnionKeys, P7 extends UnionKeys, P8 extends UnionKeys, P9 extends UnionKeys, P10 extends UnionKeys>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10): Omit export function omit, P2 extends UnionKeys, P3 extends UnionKeys, P4 extends UnionKeys, P5 extends UnionKeys, P6 extends UnionKeys, P7 extends UnionKeys, P8 extends UnionKeys, P9 extends UnionKeys, P10 extends UnionKeys, P11 extends UnionKeys>(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 export function omit, P2 extends UnionKeys, P3 extends UnionKeys, P4 extends UnionKeys, P5 extends UnionKeys, P6 extends UnionKeys, P7 extends UnionKeys, P8 extends UnionKeys, P9 extends UnionKeys, P10 extends UnionKeys, P11 extends UnionKeys, P12 extends UnionKeys>(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 +export function omit>(subject: T, ...props: Props[]): Omit export function omit(subject: T, ...props: Array>) { return reduceByKey(subject, (p, k) => { if (props.indexOf(k) === -1) p[k] = subject[k] return p - }, {} as AnyRecord) + }, record()) } diff --git a/ts/object/pick.spec.ts b/ts/object/pick.spec.ts index 6c4286724e..9289cc2ab4 100644 --- a/ts/object/pick.spec.ts +++ b/ts/object/pick.spec.ts @@ -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`, () => { + 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 = { type: 'invoke', payload: [] } + const x: Pick = { 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 = { [P in keyof T]: T[P] } - let x: Id> = { 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 = { [P in keyof T]: T[P] } + let x: Id> = { 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(input: Pick): void { - assertType.isString(input.a) - } - foo({ a: '1' }) -}) + test('intersection types with generic', () => { + type Foo = { a: string, b: string } + function foo(input: Pick): void { + assertType.isString(input.a) + } + foo({ a: '1' }) + }) -test('optional property remains optional', () => { - type Foo = { a?: string, b: string } - type A = Pick - assertType.isTrue(canAssign()({})) -}) + test('optional property remains optional', () => { + type Foo = { a?: string, b: string } + type A = Pick + assertType.isTrue(canAssign()({})) + }) -test('pick never gets empty object', () => { - type A = { a: number } - type S = Pick - type K = keyof S - assertType.isTrue(true as Equal) + test('pick never gets empty object', () => { + type A = { a: number } + type S = Pick + type K = keyof S + assertType.isTrue(true as Equal) + }) }) diff --git a/ts/object/pick.ts b/ts/object/pick.ts index 8523e6dac2..eedf550145 100644 --- a/ts/object/pick.ts +++ b/ts/object/pick.ts @@ -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, P2 extends Un export function pick, P2 extends UnionKeys, P3 extends UnionKeys, P4 extends UnionKeys, P5 extends UnionKeys, P6 extends UnionKeys, P7 extends UnionKeys, P8 extends UnionKeys, P9 extends UnionKeys, P10 extends UnionKeys>(subject: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6, prop7: P7, prop8: P8, prop9: P9, prop10: P10): Pick export function pick, P2 extends UnionKeys, P3 extends UnionKeys, P4 extends UnionKeys, P5 extends UnionKeys, P6 extends UnionKeys, P7 extends UnionKeys, P8 extends UnionKeys, P9 extends UnionKeys, P10 extends UnionKeys, P11 extends UnionKeys>(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 export function pick, P2 extends UnionKeys, P3 extends UnionKeys, P4 extends UnionKeys, P5 extends UnionKeys, P6 extends UnionKeys, P7 extends UnionKeys, P8 extends UnionKeys, P9 extends UnionKeys, P10 extends UnionKeys, P11 extends UnionKeys, P12 extends UnionKeys>(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 +export function pick>(subject: T, ...props: Props[]): Pick export function pick(subject: T, ...props: Array>) { return reduceByKey(subject, (p, k) => { if (props.indexOf(k) >= 0) p[k] = subject[k] return p - }, {} as T) + }, record()) } /**