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: [],
},
]);