Search code examples
typescripttypesconditional-typestypescript-types

How to apply the interface according to the object property value


I wrote it again by summarizing the contents of the article.

I want to know how to apply the interface according to the object property value.

I wrote a simple example code below.

There are a total of three nodes: root, dept, and user.

The type corresponding to each node is specified. Therefore, we want the interface to be designated according to the object type property.

However, you can see that you are manually affirming the type because you have not found a solution.

In order to solve this problem, I think it should be solved with an index type rather than a conditional type.

The reason is that the conditional type is expected to be calculated and returned according to the generic type parameter, but the current problem should be calculated based on the existing type rather than receiving the type parameter.

However, the problem could not be solved even with two methods. Any help would be appreciated 🙇‍♂️

export enum NodeType {
  root = "root",
  dept = "dept",
  user = "user",
}

interface Node {
  readonly type: NodeType;
  title: string;
}

interface RootNode extends Node {
  type: NodeType.root;
}

interface DeptNode extends Node {
  type: NodeType.dept;
  departmentCode: string;
}

interface UserNode extends Node {
  type: NodeType.user;
  employeeNumber: string;
}

const nodeData: Node[] = [
  {
    type: NodeType.root,
    title: "Company 1",
  } as RootNode,
  {
    type: NodeType.dept,
    title: "Department 1",
    departmentCode: "557",
    // The type is not inferred according to the object property type, so the type must be explicitly asserted.
  } as DeptNode,
  {
    type: NodeType.user,
    title: "User 1",
    employeeNumber: "201911",
  } as UserNode,
];

// I want to be the type of DeptNode interface because the type of object property is NodeType.dept.
const selectDept = nodeData.filter((x) => x.type === NodeType.dept);
selectDept.forEach((x) => {
  console.log({
    type: x.type,
    title: x.title,
    // The type is not affirmed in the node that fits the object property type, so you have to affirm the type yourself.
    departmentCode: (x as DeptNode).departmentCode,
  });
});

Solution

  • I know how to do it by using types, instead of interfaces.

    Your attempt has two problems:

    1. by using type, you can leverage the discriminated unions (notice the Node type as the union of the three possible cases).
    2. for the array methods such as filter and forEach, the inferral algorithm isn't smart enough to understand what is the requested type, hence you have to use a simple for and a if. Read more here: https://stackoverflow.com/a/62033938/632445

    Here we go:

    export enum NodeType {
        root = "root",
        dept = "dept",
        user = "user",
    }
    
    type NodeBase = {
        readonly type: NodeType;
        title: string;
    }
    
    type RootNode = NodeBase & {
        type: NodeType.root;
    }
    
    type DeptNode = NodeBase & {
        type: NodeType.dept;
        departmentCode: string;
    }
    
    type UserNode = NodeBase & {
        type: NodeType.user;
        employeeNumber: string;
    }
    
    type Node = RootNode | DeptNode | UserNode;
    
    const nodeData: Node[] = [
        {
            type: NodeType.root,
            title: "Company 1",
        },
        {
            type: NodeType.dept,
            title: "Department 1",
            departmentCode: "557",
            // The type is not inferred according to the object property type, so the type must be explicitly asserted.
        },
        {
            type: NodeType.user,
            title: "User 1",
            employeeNumber: "201911",
        },
    ];
    
    // I want to be the type of DeptNode interface because the type of object property is NodeType.dept.
    for (const x of nodeData) {
        if (x.type === NodeType.dept) {
            console.log({
                type: x.type,
                title: x.title,
                // The type is not affirmed in the node that fits the object property type, so you have to affirm the type yourself.
                departmentCode: (x as DeptNode).departmentCode,
            });
        }
    }