Home > database >  How to derive which type matches a generic?
How to derive which type matches a generic?

Time:03-02

Given the following types…

interface ParentNode {
    items: Node[]
}

interface LeafNode {
}

type Node = ParentNode | LeafNode

function List<Nodes extends unknown[]>(items: Nodes)

…how can I derive whether the unknown type passed to Nodes matches the ParentNode type (i.e. has items property) or a LeafNode type?

interface A1 {
    items: []
}

interface A2 {
    name: string;
}

List<A1|A2>([
    { name: 'Hello' },
    { items: [
          { name: 'Bye' }
          { items: [] }
      ] }
])

// in the List function

if ('items' in item) {
    let typedItem = item as A1 // desired effect but I'm aware it's not possible like this
} else {
    let typedItem = item as A2 // desired effect but I'm aware it's not possible like this
}

How can I do this even though the List function does not know which types Nodes can be in advance?

CodePudding user response:

If I understand correctly, I would add an attribute, say type, in the interface to assist the process:

interface Base {
  type: 'foo' | 'bar';
  // ... some other common attr
}

// Leaf
interface Bar extends Base {
  type: 'bar';
  name: string;
  // attrs?: any[];
  // items?: Bar[];
}

// Parent
interface Foo extends Base {
  type: 'foo';
  items: Bar[];
}

type Baz = Foo | Bar; // no `Base` here

function isBaz(item: unknown): item is Baz {
  if (typeof item !== 'object' || item === null) {
    return false;
  }

  if (!('type' in item)) {
    return false;
  }

  if (!('items' in item) && !('name' in item)) {
    return false;
  }

  // more checks
  return true;
}

function List<T extends unknown = unknown>(items: T[]) {
  // items is array of anything
  items.forEach((item) => {
    if (isBaz(item)) {
      // item is now T & Baz
      if (item.type === 'foo') {
        // item should be `Foo` here
        console.log(item.items); // []
        return;
      }
  
      // item should be `Bar` here
      console.log(item.name); // Hello
    }
  });
}

List([
  {
    type: 'bar',
    name: 'Hello',
  },
  {
    type: 'foo',
    items: [],
  },
]);
  • Related