I'm trying to extract all the values of the keys "id" in the object, at the first level and at all the nested levels.
Given my ExampleType
, I want NestedID<ExampleType>
to be "one" | "two" | "three" | "four"
. However, I'm doing something wrong and it only gives me "one"
, and I don't understand why.
Does somebody understands why this is not working ?
I got the inspiration from https://dev.to/pffigueiredo/typescript-utility-keyof-nested-object-2pa3 which is a really good tutorial.
type ExampleType = {
id: "one"
children: [{ id: "two" }, { id: "three"; children: [{ id: "four" }] }, { abc: "def" }]
efg: "hij"
}
type NestedId<ObjectType extends Record<string, any> | undefined> = {
[Key in keyof ObjectType & string]: ObjectType[Key] extends undefined
? never
: Key extends "id" // if the key is "id"
? ObjectType[Key] // return the value of the key
: Key extends "children" // else, if the key is "children"
? ObjectType[Key] extends Array<Record<string, any> | undefined> // and its value is an array of object
? NestedId<ObjectType[Key][number]> // recursive call
: never // else, children value is not an object --> return never
: never // else, key is not "id" or "children" --> return never
}[keyof ObjectType & string]
const id1: NestedId<ExampleType> = "one" // works as expected
const id2: NestedId<ExampleType> = "two" // doesn't work (Type "two" is not assignable to type "one")
const id3: NestedId<ExampleType> = "three" // doesn't work (Type "three" is not assignable to type "one")
const id4: NestedId<ExampleType> = "four" // doesn't work (Type "four" is not assignable to type "one")
const foo: NestedId<ExampleType> = "foo" // works as expected
const bar: NestedId<ExampleType> = "bar" // works as expected
You can use infer
to achieve this. It gets the parent id and recursively checks the children props to add the ids in from there if they exist.
type NestedId<ObjectType> =
ObjectType extends { id: infer IdType }
? IdType | (ObjectType extends { children: Array<infer ChildType> }
? NestedId<ChildType>
: never)
: never;
In this example you get "one" | "two" | "three" | "four"
back, and everything else like "foo"
will error.