Skip to content

Commit

Permalink
PickDeep: Support interface (#755)
Browse files Browse the repository at this point in the history
  • Loading branch information
Emiyaaaaa authored Nov 15, 2023
1 parent 972815c commit 1c65935
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 21 deletions.
2 changes: 1 addition & 1 deletion source/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type B = BuildObject<'a', string, {readonly a?: any}>;
//=> {readonly a?: string}
```
*/
export type BuildObject<Key extends PropertyKey, Value, CopiedFrom extends UnknownRecord = {}> =
export type BuildObject<Key extends PropertyKey, Value, CopiedFrom extends object = {}> =
Key extends keyof CopiedFrom
? Pick<{[_ in keyof CopiedFrom]: Value}, Key>
: Key extends `${infer NumberKey extends number}`
Expand Down
45 changes: 26 additions & 19 deletions source/pick-deep.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {BuildObject, BuildTuple, ToString} from './internal';
import type {BuildObject, BuildTuple, NonRecursiveType, ObjectValue} from './internal';
import type {IsNever} from './is-never';
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.
Expand Down Expand Up @@ -76,37 +76,44 @@ type Street = PickDeep<Configuration, 'userConfig.address.1.street2'>;
@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]>>
export type PickDeep<T, PathUnion extends Paths<T>> =
T extends NonRecursiveType
? never
: T extends UnknownArray
? UnionToIntersection<{
[P in PathUnion]: InternalPickDeep<T, P>;
}[PathUnion]
>
: never;
: T extends object
? Simplify<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;
type InternalPickDeep<T, Path extends string | number> =
T extends NonRecursiveType
? never
: T extends UnknownArray ? PickDeepArray<T, Path>
: T extends object ? Simplify<PickDeepObject<T, Path>>
: never;

/**
Pick an object from the given object by one path.
*/
type PickDeepObject<RecordType extends UnknownRecord, P extends string | number> =
type PickDeepObject<RecordType extends object, 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>
? ObjectValue<RecordType, RecordKeyInPath> extends infer ObjectV
? IsNever<ObjectV> extends false
? BuildObject<RecordKeyInPath, InternalPickDeep<NonNullable<ObjectV>, SubPath>, RecordType>
: never
: never
: ObjectValue<RecordType, P> extends infer ObjectV
? IsNever<ObjectV> extends false
? BuildObject<P, ObjectV, RecordType>
: never
: never;

/**
Expand Down
18 changes: 17 additions & 1 deletion test-d/pick-deep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,29 @@ type DeepType = {
deep: {
deeper: {
value: string;
value1: number;
};
};
};
foo: string;
};
type DepthType = {nested: {deep: {deeper: {value: string}}}};

declare const deep: PickDeep<DeepType, 'nested.deep.deeper.value'>;
expectType<{nested: {deep: {deeper: {value: string}}}}>(deep);
expectType<DepthType>(deep);

// Test interface
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface DeepInterface extends DeepType {
bar: {
number: number;
string: string;
};
}
declare const deepInterface: PickDeep<DeepInterface, 'nested.deep.deeper.value'>;
expectType<DepthType>(deepInterface);
declare const deepInterface2: PickDeep<DeepInterface, 'bar.number'>;
expectType<{bar: {number: number}}>(deepInterface2);

type GenericType<T> = {
genericKey: T;
Expand Down

0 comments on commit 1c65935

Please sign in to comment.