I want to specify a type called Item
which has the possibility of being nested as follows:
Either
type Item {
...;
item: Item;
answer: {
...;
//(does not contain property "item")
}
}
OR
type Item {
...;
//(does not contain property "item")
answer: {
...;
item: Item
}
}
i.e. either the Item
type can have item:Item
as a direct child property, or as a grandchild below the property answer
but not both.
Context: I'm trying to implement the item property of the QuestionnaireResponse resource from the FHIR specification
CodePudding user response:
This should do the trick:
type FirstOrSecondLayer<T, I, A extends string> = XOR<T & I, T & Record<A, I>>
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
type Item = Expand<FirstOrSecondLayer<{
a: string
b: string
answer: {
c: string
}
},{
item: Item
}, "answer">
>
There is a lot of stuff going on here. I added some extra properties a
, b
and c
for demonstration purposes. You pass the type without item
as T
. I
is the exclusive object type. And for A
you pass the name of the property which nests I
.
I used the Expand
type from here and XOR
from here.
Here are some test cases:
const t1: Item = {
a: "",
b: "",
item: {} as Item,
answer: {
c: ""
}
} // works
const t2: Item = {
a: "",
b: "",
answer: {
c: "",
item: {} as Item,
}
} // works
const t3: Item = {
a: "",
b: "",
item: {} as Item,
answer: {
c: "",
item: {} as Item, // error
}
}
Let me know if this works for your use case.