Skip to content

Instantly share code, notes, and snippets.

@Quramy
Last active August 1, 2024 16:06
Show Gist options
  • Save Quramy/d2a3b6eb5a5edf469fea0546f1118561 to your computer and use it in GitHub Desktop.
Save Quramy/d2a3b6eb5a5edf469fea0546f1118561 to your computer and use it in GitHub Desktop.
Util type to pick object recursively
namespace type_test_for_DeepPick {
type BlogPost = {
id: string;
isDraft: boolean;
title: string;
subTitle?: string;
body: string;
author?: {
id: string;
firstName: string;
lastName: string;
createdAt: string;
updatedAt: string;
};
tags?: {
id: string;
color: string;
label: string;
createdAt: string;
updatedAt: string;
}[];
createdAt: string;
updatedAt: string;
};
namespace example {
const testObj: DeepPick<BlogPost, "id" | "author.id" | "author.firstName" | "tags.label"> = {
id: "b0001",
author: {
id: "user001",
firstName: "Bob",
},
tags: [{ label: "Programming" }, { label: "TypeScript" }],
};
// @ts-expect-error
type WithInvalidPath = DeepPick<BlogPost, "id" | "not_existing_key">;
}
namespace simple_path {
const testObj: DeepPick<BlogPost, "id" | "title" | "author"> = {
id: "b0001",
title: "awesome blog",
author: {
id: "user001",
firstName: "Bob",
lastName: "Anderson",
createdAt: "2020-01-01T09:00:00.000Z",
updatedAt: "2020-01-01T09:00:00.000Z",
},
};
const testObjAlt = testObj satisfies Pick<BlogPost, "id" | "title" | "author">;
}
namespace path_for_nested_record {
const testObj: DeepPick<BlogPost, "id" | "author.id" | "author.firstName"> = {
id: "b0001",
author: {
id: "user001",
firstName: "Bob",
},
};
}
namespace path_for_nested_array {
const testObj: DeepPick<BlogPost, "id" | "tags.id" | "tags.color"> = {
id: "b0001",
tags: [
{
id: "tag001",
color: "#ff0000",
},
],
};
const testObjAlt: DeepPick<BlogPost, "id" | "tags.id" | "tags.color"> = {
id: "b0001",
tags: undefined,
};
}
namespace type_test_for_PathForDeepPick {
type CircularRefType = { id: string; children?: CircularRefType[] };
type TestPathCircularRefType = PathForDeepPick<CircularRefType>; // string
}
}
type _s = symbol;
declare namespace Nat {
export type Nat = readonly _s[];
export type Zero = [];
export type Increment<Value extends Nat> = [_s, ...Value];
export type ToNumber<Value extends Nat> = Value["length"];
export type From<N extends number> = N extends 0
? Zero
: N extends 1
? [_s]
: N extends 2
? [_s, _s]
: N extends 3
? [_s, _s, _s]
: N extends 4
? [_s, _s, _s, _s]
: N extends 5
? [_s, _s, _s, _s, _s]
: N extends 6
? [_s, _s, _s, _s, _s, _s]
: never;
}
type DottedChildrenPath<Path extends string, ParentKey extends string> = Path extends ParentKey
? string
: Path extends `${ParentKey}.${infer ChildPath}`
? ChildPath
: never;
type DottedParentKey<Path extends string> = Path extends `${infer ParentKey}.${string}`
? ParentKey
: Path;
type MaxRecursionDepth = Nat.From<4>;
type DottedPath<
T,
ParentKey extends string = "",
RecursionDepth extends Nat.Nat = Nat.Zero,
> = RecursionDepth extends MaxRecursionDepth
? string
: T extends readonly (infer TItem)[]
? DottedPath<TItem, ParentKey, Nat.Increment<RecursionDepth>>
: T extends Record<string, unknown>
? Exclude<
{
[Key in keyof T]: Key extends string
? DottedPath<
T[Key],
ParentKey extends "" ? Key : `${ParentKey}.${Key}`,
Nat.Increment<RecursionDepth>
>
: never;
}[keyof T],
undefined
>
: ParentKey;
type Primitive = undefined | null | boolean | number | string | bigint | symbol;
type ValueOrRecursivePick<TValue, Path extends string> = TValue extends Primitive
? TValue
: TValue extends readonly (infer TItem)[]
? ValueOrRecursivePick<TItem, Path>[]
: TValue extends Record<string, unknown>
? DeepPickInternal<TValue, Path>
: never;
type DeepPickInternal<TObject extends Record<string, unknown>, Path extends string> = {
[Key in Extract<keyof TObject, DottedParentKey<Path>>]: ValueOrRecursivePick<
TObject[Key],
DottedChildrenPath<Path, Key>
>;
};
export type PathForDeepPick<T> = DottedPath<T, "">;
export type DeepPick<
TObject extends Record<string, unknown>,
Path extends PathForDeepPick<TObject>,
> = DeepPickInternal<TObject, Path>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment