Home > database >  Utility type to filter generic types by a static property
Utility type to filter generic types by a static property

Time:08-28

I would like to filter a specific generic type according to a static property. It's easier to show than to explain. I am happy to rephrase the question if anyone can give me a hint how to describe it better (things are quite abstract for a TS novice like me).

// My given types
interface ChardataNode {
    type: "Chardata"
    value: string
}
interface ElementNode<TName extends string = string, TChild extends Child = Child> {
    type: "Element"
    name: TName
    children: TChild[]
}
type Child = ElementNode | ChardataNode

// This is what I tried. Unfortunately, the first utility type does not work at all as it doesn't filter out the element children 
type ElementChildren<TParent> = TParent extends ElementNode ? TParent["children"][0]["type"] extends "Element" ? TParent["children"] : never : never
type ElementNames<TElements> = TElements extends ElementNode[] ? TElements[0]["name"] : never

// Setup an example type for testing
type X = ElementNode<"Root", ChardataNode | ElementNode<"Child1", never> | ElementNode<"Child2", never>>

// What I want is: "Child1" | "Child2", but what I get is: never
type Names = ElementNames<ElementChildren<X>>

Typescript Playground

CodePudding user response:

In the end, I came up with this solution:

// My given types
interface ChardataNode {
    type: "Chardata"
    value: string
}
interface ElementNode<TName extends string = string, TChild extends Child = Child> {
    type: "Element"
    name: TName
    children: TChild[]
}
type Child = ElementNode | ChardataNode

// The fixed utility types 
type ElementChildren<TParent> = TParent extends ElementNode ? Extract<TParent["children"][0], ElementNode> : never
type ElementNames<TElement> = TElement extends ElementNode ? TElement["name"] : never

// Setup an example type for testing
type X = ElementNode<"Root", ChardataNode | ElementNode<"Child1", never> | ElementNode<"Child2", never>>

// Results in "Child1" | "Child2"
type Names = ElementNames<ElementChildren<X>>

Typescript Playground

CodePudding user response:

The part with the array was confusing the compiler.
You have to have a separate step to check for array and a separate step for the array element.

interface ChardataNode {
    type: 'Chardata'
    value: string
}
interface ElementNode<TName extends string = string, TChild extends Child = Child> {
    type: 'Element'
    name: TName
    children: TChild[]
}
type Child = ElementNode | ChardataNode

// It's not necessary to check the type of children if you're only interested in the name
type ElementChildren<TParent> = TParent extends ElementNode ? TParent['children'] : never

type ElementName<TElement> = TElement extends ElementNode ? TElement['name'] : never
type ElementsName<TElements> = TElements extends any[] ? ElementName<TElements[0]> : never


// Set up an example type for testing
type X = ElementNode<'Root', ChardataNode | ElementNode<'Child1', never> | ElementNode<'Child2', never>>

// XChildren is an array of ChardataNode | ElementNode<'Child1', never> | ElementNode<'Child2', never>
// but that's not a problem for extracting the name
type XChildren = ElementChildren<X>
// XChildName is 'Child1' | 'Child2'
type XChildName = ElementsName<XChildren>

// Alternative if you're not working with arrays
type XChild = ElementChildren<X>[0]
type XChildNameAlt = ElementName<XChild>


// Utility in case you're looking to extract just children of a certain type
type ElementChildOfType<TParent, TChild> = Extract<ElementChildren<TParent>[0], TChild>
type ElementChildOfTypeElementNode<TParent> = ElementChildOfType<TParent, ElementNode>

// XChildElement is ElementNode<'Child1', never> | ElementNode<'Child2', never>
type XChildElement = ElementChildOfTypeElementNode<X>

Tested with v4.7.4

Edit:
You had an unnecessary condition in ElementChildren, which was not necessary for extracting the names.
I've created a separate type for extracting elements of a certain type, if you specifically need that.

  • Related