-
-
Notifications
You must be signed in to change notification settings - Fork 569
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
- Loading branch information
1 parent
86ddc1a
commit c60caba
Showing
6 changed files
with
288 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import type {BuildObject, BuildTuple, ToString} from './internal'; | ||
import type {Paths} from './paths'; | ||
import type {Simplify} from './simplify.d'; | ||
import type {UnionToIntersection} from './union-to-intersection.d'; | ||
import type {UnknownArray} from './unknown-array'; | ||
import type {UnknownRecord} from './unknown-record.d'; | ||
|
||
/** | ||
Pick properties from a deeply-nested object. | ||
It supports recursing into arrays. | ||
Use-case: Distill complex objects down to the components you need to target. | ||
@example | ||
``` | ||
import type {PickDeep, PartialDeep} from 'type-fest'; | ||
type Configuration = { | ||
userConfig: { | ||
name: string; | ||
age: number; | ||
address: [ | ||
{ | ||
city1: string; | ||
street1: string; | ||
}, | ||
{ | ||
city2: string; | ||
street2: string; | ||
} | ||
] | ||
}; | ||
otherConfig: any; | ||
}; | ||
type NameConfig = PickDeep<Configuration, 'userConfig.name'>; | ||
// type NameConfig = { | ||
// userConfig: { | ||
// name: string; | ||
// }; | ||
// Supports optional properties | ||
type User = PickDeep<PartialDeep<Configuration>, 'userConfig.name' | 'userConfig.age'>; | ||
// type User = { | ||
// userConfig?: { | ||
// name?: string; | ||
// age?: number; | ||
// }; | ||
// }; | ||
// Supports array | ||
type AddressConfig = PickDeep<Configuration, `userConfig.address.0`>; | ||
// type AddressConfig = { | ||
// userConfig: { | ||
// address: [{ | ||
// city1: string; | ||
// street1: string; | ||
// }]; | ||
// }; | ||
// } | ||
// Supports recurse into array | ||
type Street = PickDeep<Configuration, `userConfig.address.1.street2`>; | ||
// type AddressConfig = { | ||
// userConfig: { | ||
// address: [ | ||
// unknown, | ||
// {street2: string} | ||
// ]; | ||
// }; | ||
// } | ||
``` | ||
@category Object | ||
@category Array | ||
*/ | ||
export type PickDeep<T extends UnknownRecord | UnknownArray, PathUnion extends Paths<T>> = | ||
T extends UnknownRecord | ||
? Simplify<UnionToIntersection<{ | ||
[P in PathUnion]: InternalPickDeep<T, P>; | ||
}[PathUnion]>> | ||
: T extends UnknownArray | ||
? UnionToIntersection<{ | ||
[P in PathUnion]: InternalPickDeep<T, P>; | ||
}[PathUnion] | ||
> | ||
: never; | ||
|
||
/** | ||
Pick an object/array from the given object/array by one path. | ||
*/ | ||
type InternalPickDeep< | ||
T extends UnknownRecord | UnknownArray, | ||
Path extends string | number, // Checked paths, extracted from unchecked paths | ||
> = | ||
T extends UnknownArray ? PickDeepArray<T, Path> | ||
: T extends UnknownRecord ? Simplify<PickDeepObject<T, Path>> | ||
: never; | ||
|
||
/** | ||
Pick an object from the given object by one path. | ||
*/ | ||
type PickDeepObject<RecordType extends UnknownRecord, P extends string | number> = | ||
P extends `${infer RecordKeyInPath}.${infer SubPath}` | ||
? BuildObject<RecordKeyInPath, InternalPickDeep<NonNullable<RecordType[RecordKeyInPath]>, SubPath>, RecordType> | ||
: P extends keyof RecordType | ToString<keyof RecordType> // Handle number keys | ||
? BuildObject<P, RecordType[P], RecordType> | ||
: never; | ||
|
||
/** | ||
Pick an array from the given array by one path. | ||
*/ | ||
type PickDeepArray<ArrayType extends UnknownArray, P extends string | number> = | ||
// Handle paths that are `${number}.${string}` | ||
P extends `${infer ArrayIndex extends number}.${infer SubPath}` | ||
// When `ArrayIndex` is equal to `number` | ||
? number extends ArrayIndex | ||
? ArrayType extends unknown[] | ||
? Array<InternalPickDeep<NonNullable<ArrayType[number]>, SubPath>> | ||
: ArrayType extends readonly unknown[] | ||
? ReadonlyArray<InternalPickDeep<NonNullable<ArrayType[number]>, SubPath>> | ||
: never | ||
// When `ArrayIndex` is a number literal | ||
: ArrayType extends unknown[] | ||
? [...BuildTuple<ArrayIndex>, InternalPickDeep<NonNullable<ArrayType[ArrayIndex]>, SubPath>] | ||
: ArrayType extends readonly unknown[] | ||
? readonly [...BuildTuple<ArrayIndex>, InternalPickDeep<NonNullable<ArrayType[ArrayIndex]>, SubPath>] | ||
: never | ||
// When the path is equal to `number` | ||
: P extends `${infer ArrayIndex extends number}` | ||
// When `ArrayIndex` is `number` | ||
? number extends ArrayIndex | ||
? ArrayType | ||
// When `ArrayIndex` is a number literal | ||
: ArrayType extends unknown[] | ||
? [...BuildTuple<ArrayIndex>, ArrayType[ArrayIndex]] | ||
: ArrayType extends readonly unknown[] | ||
? readonly [...BuildTuple<ArrayIndex>, ArrayType[ArrayIndex]] | ||
: never | ||
: never; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import {expectType} from 'tsd'; | ||
import type {PickDeep} from '../index'; | ||
|
||
declare class ClassA { | ||
a: string; | ||
} | ||
|
||
type BaseType = { | ||
string: string; | ||
optionalString?: string; | ||
array: number[]; | ||
readonlyArray: readonly number[]; | ||
tuples: ['foo', 'bar']; | ||
objectArray: Array<{a: 1; b: 2}>; | ||
leadingSpreadArray: [...Array<{a: 1}>, {b: 2}]; | ||
tailingSpreadArray: [{a: 1}, {b: {c: 2; other: 2}}, ...Array<{d: 3}>]; | ||
objectTuple: [{a: 1}]; | ||
number: number; | ||
boolean: boolean; | ||
date: Date; | ||
Class: typeof ClassA; | ||
instance: ClassA; | ||
0: number; | ||
}; | ||
|
||
type Testing = BaseType & { | ||
object: BaseType; | ||
optionalObject?: Partial<BaseType>; | ||
optionalString?: string; | ||
readonly readonlyObject: {a: 1}; | ||
1: BaseType; | ||
2?: BaseType; | ||
}; | ||
|
||
declare const normal: PickDeep<Testing, 'string'>; | ||
expectType<{string: string}>(normal); | ||
|
||
type DeepType = { | ||
nested: { | ||
deep: { | ||
deeper: { | ||
value: string; | ||
}; | ||
}; | ||
}; | ||
foo: string; | ||
}; | ||
declare const deep: PickDeep<DeepType, 'nested.deep.deeper.value'>; | ||
expectType<{nested: {deep: {deeper: {value: string}}}}>(deep); | ||
|
||
type GenericType<T> = { | ||
genericKey: T; | ||
}; | ||
declare const genericTest: PickDeep<GenericType<number>, 'genericKey'>; | ||
expectType<{genericKey: number}>(genericTest); | ||
|
||
declare const union: PickDeep<Testing, 'object.number' | 'object.string'>; | ||
expectType<{object: {number: number} & {string: string}}>(union); | ||
|
||
declare const optional: PickDeep<Testing, 'optionalObject.optionalString'>; | ||
expectType<{optionalObject?: {optionalString?: string}}>(optional); | ||
|
||
declare const optionalUnion: PickDeep<Testing, 'optionalObject.string' | 'object.number'>; | ||
expectType<{optionalObject?: {string?: string}; object: {number: number}}>(optionalUnion); | ||
|
||
declare const readonlyTest: PickDeep<Testing, 'readonlyObject.a'>; | ||
expectType<{readonly readonlyObject: {a: 1}}>(readonlyTest); | ||
|
||
declare const array: PickDeep<Testing, 'object.array'>; | ||
expectType<{object: {array: number[]}}>(array); | ||
|
||
declare const readonlyArray: PickDeep<Testing, 'object.readonlyArray'>; | ||
expectType<{object: {readonlyArray: readonly number[]}}>(readonlyArray); | ||
|
||
declare const tuple: PickDeep<Testing, 'object.tuples'>; | ||
expectType<{object: {tuples: ['foo', 'bar']}}>(tuple); | ||
|
||
declare const objectArray1: PickDeep<Testing, `object.objectArray.${number}`>; | ||
expectType<{object: {objectArray: Array<{a: 1; b: 2}>}}>(objectArray1); | ||
|
||
declare const objectArray2: PickDeep<Testing, `object.objectArray.${number}.a`>; | ||
expectType<{object: {objectArray: Array<{a: 1}>}}>(objectArray2); | ||
|
||
declare const leadingSpreadArray1: PickDeep<Testing, `object.leadingSpreadArray.${number}.a`>; | ||
expectType<{object: {leadingSpreadArray: [...Array<{a: 1}>]}}>(leadingSpreadArray1); | ||
|
||
declare const leadingSpreadArray2: PickDeep<Testing, `object.leadingSpreadArray.${number}`>; | ||
expectType<{object: {leadingSpreadArray: [...Array<{a: 1}>, {b: 2}]}}>(leadingSpreadArray2); | ||
|
||
declare const tailingSpreadArray1: PickDeep<Testing, 'object.tailingSpreadArray.1'>; | ||
expectType<{object: {tailingSpreadArray: [unknown, {b: {c: 2; other: 2}}]}}>(tailingSpreadArray1); | ||
|
||
declare const tailingSpreadArray2: PickDeep<Testing, 'object.tailingSpreadArray.1.b.c'>; | ||
expectType<{object: {tailingSpreadArray: [unknown, {b: {c: 2}}]}}>(tailingSpreadArray2); | ||
|
||
declare const date: PickDeep<Testing, 'object.date'>; | ||
expectType<{object: {date: Date}}>(date); | ||
|
||
declare const instance: PickDeep<Testing, 'object.instance'>; | ||
expectType<{object: {instance: ClassA}}>(instance); | ||
|
||
declare const classTest: PickDeep<Testing, 'object.Class'>; | ||
expectType<{object: {Class: typeof ClassA}}>(classTest); | ||
|
||
declare const numberTest: PickDeep<Testing, '1'>; | ||
expectType<{1: BaseType}>(numberTest); | ||
|
||
declare const numberTest2: PickDeep<Testing, '1.0'>; | ||
expectType<{1: {0: number}}>(numberTest2); | ||
|
||
declare const numberTest3: PickDeep<Testing, '2.0'>; | ||
expectType<{2?: {0: number}}>(numberTest3); |