Last active
June 26, 2022 21:21
-
-
Save tokland/0494a0360a32b2aa271034e2bbf0bcd5 to your computer and use it in GitHub Desktop.
SelectorPick<Model, Selector>: An exhanced version of Pick<T, Keys> to select nested properties from T
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
/* SelectedPick<T, Selector> | |
An extended version of Pick<T, Key> where, instead of a union of keys, | |
you pass an object with the properties to get from a type. */ | |
type Extends<T1, T2> = [T1] extends [T2] ? true : false | |
type OmitNever<T> = Pick<T, { [K in keyof T]: T[K] extends never ? never : K }[keyof T]> | |
export type Selector<Model> = { | |
[Key in keyof Model]?: boolean | NestedSelectorValue<Model[Key]> | |
} | |
type NestedSelectorValue<ModelValue> = ModelValue extends Array<infer InnerModel> | |
? Selector<InnerModel> | |
: ModelValue extends object | |
? Selector<ModelValue> | |
: never | |
export type SelectedPick<Model, ModelSelector extends Selector<Model>> = OmitNever<{ | |
[Key in keyof Model & keyof ModelSelector]: SelectedPickValue<Model[Key], ModelSelector[Key]> | |
}> | |
type SelectedPickValue<ModelValue, SelectorValue> = SelectorValue extends true | |
? ModelValue | |
: Extends<SelectorValue, object> extends false | |
? never | |
: ModelValue extends Array<infer InnerModel> | |
? Array<SelectedPick<InnerModel, SelectorValue>> | |
: SelectedPick<ModelValue, SelectorValue> | |
/* Example */ | |
interface User { | |
id: number | |
name: string | |
addresses: Address[] | |
} | |
interface Address { | |
id: number | |
city: City | |
} | |
interface City { | |
id: number | |
name: string | |
} | |
type SelectedUser<UserSelector> = SelectedPick<User, UserSelector> | |
// Assert helpers | |
type ConstraintOn<T extends boolean, U = unknown> = T extends true ? U : never | |
type AssertEqual<T1 extends ConstraintOn<Extends<T1, T2>>, T2 extends ConstraintOn<Extends<T2, T1>>> = T1 | |
type _AssertSelectedPickReturnsPlainKeys = AssertEqual< | |
SelectedUser<{ id: true; name: true }>, | |
{ id: number; name: string } | |
> | |
type _AssertSelectedPickSkipsFalseValues = AssertEqual< | |
SelectedUser<{ id: true; name: false }>, | |
{ id: number } | |
> | |
type _AssertSelectedPickReturnsFullArrayOnTrueValue = AssertEqual< | |
SelectedUser<{ addresses: true }>, | |
{ addresses: Address[] } | |
> | |
type _AssertSelectedPickReturnsNestedObject = AssertEqual< | |
SelectedUser<{ | |
id: true | |
addresses: { id: true; city: { name: true } } | |
}>, | |
{ | |
id: number | |
addresses: Array<{ id: number; city: { name: string } }> | |
} | |
> | |
// A use-case: get data from some external API that supports arbitrary nested fields. | |
declare function getUserFromApi<UserSelector extends Selector<User>>( | |
selector: UserSelector | |
): SelectedPick<User, UserSelector> | |
const user = getUserFromApi({ | |
id: true, | |
addresses: { | |
id: true, | |
city: true, | |
}, | |
}) | |
const _cityName = user.addresses[0]?.city?.name |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment