Search code examples
javascripttypescriptrecursive-type

How to write conditional recursive types on an object in typescript


I have following four types

Type T1

export type T1 = "p" | "q" | "r" | "a" | "b";

Type T2

export type T2 = {
  h: string;
  hc: {};
};

Interface Def

export interface Def {
  [k: string]: {
    [K in T1 | "abc"]?: K extends T1
      ? T2
      : {
          [k in T1]?: T2;
        };
  };
}

Interface A

export interface A {
  name: string;
  def: Def;
}

The usage should be like follows

const x: A = {
  name: "test",
  def: {
    xxx: {
      p: {
        h: "s",
        hc: {
         
        },
      },
      abc: {
        p: {
          h: "s",
          hc: {
            
          },
        },
        abc: {
          p: {
            h: "s",
            hc: {
              
            },
          },
        },
      },
    },
  },
}; 

The problem I am having is that if the object of type Def with in attribute xxx has property abc then it should recursively accept the abc object again as above example. I tried calling Def recursively but that does not work.


Solution

  • I would suggest writing Def like

    interface Def {
      [k: string]: DefEntry
    }
    

    where DefEntry is a recursive object type:

    type DefEntry =
      { [K in T1]?: T2 } & // this is equivalent to Partial<Record<T1, T2>>
      { abc?: DefEntry };
    

    or, equivalently, you could write DefEntry as a recursive interface like

    interface DefEntry extends Partial<Record<T1, T2>> {
      abc?: DefEntry
    }
    

    In neither case is conditional types like K extends T1 ? T2 : ... necessary.


    And that makes your A interface and the test value work as desired:

    const x: A = { // okay
      name: "test",
      def: {
        xxx: {
          p: { h: "s", hc: {}, },
          abc: {
            p: { h: "s", hc: {}, },
            abc: {
              p: { h: "s", hc: {}, },
            },
          },
        },
      },
    }; 
    

    Playground link to code