In typescript how to restrict the list of properties passed to a function as a parameter?

I have this:

export interface RegionNode {
    nodeSelected: boolean;
    nodeEditable: boolean;
    zone: Partial<Zone>;
    parent: RegionNode | null;
    children: RegionNode[];
}

And I would like a generic function doing this:

function setNodeAndChildrenProperty(node: RegionNode, property: keyof RegionNode, state: boolean): void {
    // @ts-ignore
    node[property] = state;
    node.children.forEach((child) => {
        setNodeAndChildrenProperty(child, property, state);
    });
}

But as you can see I had to use @ts-ignore, as I don’t know how to restrict the list of allowed properties to "nodeSelected" and "nodeEditable" among other issues.

What is the elegant way to solve this problem ?

>Solution :

You can do that by having a mapped type that will return a type for just the properties assignable to a given type (in your case, boolean):

type PropertiesOfType<ObjType, PropType> = {
    [Key in keyof ObjType as ObjType[Key] extends PropType ? Key : never]: ObjType[Key];
};

The as clause in the [Key in ____] omits keys (by using never) for properties that aren’t assignable to the target property type. So for instance, PropertiesOfType<RegionNode, boolean> gives us this type:

{
    nodeSelected: boolean;
    nodeEditable: boolean;
}

Then you can use that in your function, so TypeScript knows that the type of node[property] is compatible with the type of state:

function setNodeAndChildrenProperty(
    node: RegionNode,
    property: keyof PropertiesOfType<RegionNode, boolean>, // <====
    state: boolean
): void {
    node[property] = state;
    node.children.forEach((child) => {
        setNodeAndChildrenProperty(child, property, state);
    });
}

Playground link

Leave a Reply