Skip to content

Instantly share code, notes, and snippets.

@tokland
Last active June 26, 2022 21:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tokland/0494a0360a32b2aa271034e2bbf0bcd5 to your computer and use it in GitHub Desktop.
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
/* 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