Search code examples
typescripttypescript-generics

How to make this type's field name generic?


I have no clues on how to solve this problem.

At the moment, I have this recursive interface definition as base for a generic tree structure:

interface TreeNodeBase<TNode extends TreeNodeBase<TNode, TData>, TData> {
  readonly id: string;
  readonly data: TData;
  readonly children?: ReadonlyArray<TNode>;
}

To specialize a particular tree structure, I write something like this:

type MyTreeNode = TreeNodeBase<MyTreeNode, MyTreeNodeData>;

type MyTreeNodeData = string | number;

Note: I must use an interface, because the circular reference in the specialization (MyTreeNode).

All that works like a charm.

Now, my goal is to make the "id" field configurable, because sometimes that field should better name "nodeId" or whatever string. In other words, I'd like to rewrite my original structure as:

interface TreeNodeBase<TNode extends TreeNodeBase<TNode, TData, TKey>, TData, TKey extends string = "id"> {
  readonly id: string;  //how to adjust this field???
  readonly data: TData;
  readonly children?: ReadonlyArray<TNode>;
}

However, I couldn't figure out the solution, if does exist.

I tried to leverage the mapped types, but unfortunately that won't apply to interfaces:

type TreeNodeKey<TKey extends string ="id"> = {
  readonly [P in TKey]: string;
}

interface TreeNodeBase2<TNode extends TreeNodeBase2<TNode, TData, TKey>, TData, TKey extends string> extends TreeNodeKey<TKey> {
  readonly data: TData;
  readonly children?: ReadonlyArray<TNode>;
} 

Is there any solution?


Solution

  • If you remove the recursion from the generic params you could use types:

    Playground

    type TreeNodeBase<TData, TNode extends object = {} /* allows add extra props to a node */, TKey extends string = "id", > = TNode & {
      readonly data: TData;
      readonly children?: ReadonlyArray<TreeNodeBase<TData, TNode, TKey>>;
    } & {readonly [K in TKey]: string}
    
    type BooleanTree1 = TreeNodeBase<boolean, {prop1: boolean}>;
    type BooleanTree2 = TreeNodeBase<boolean, {prop2: boolean}>;
    
    const booleanTree1: BooleanTree1 = {
      id: 'id2',
      data: true,
      prop1: true,
      children: [{
        id: 'id2',
        data: false,
        prop2: true // error
      }]
    }