Home > Enterprise >  Can I specify a recursive property rule based on whether or not it is present in a descendant?
Can I specify a recursive property rule based on whether or not it is present in a descendant?

Time:05-10

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.

Playground

  • Related