Home > OS >  Type guards not detecting cloned objects
Type guards not detecting cloned objects

Time:08-20

Why does type guard work for the object itself, but not for the clones of that object? Here is my code (enter image description here enter image description here

Here are the types:

export type Node = {
data: NodeData
id: string;
}
export type NodeData = {
  label?: string;
  children: NodeDataChildren;
};

type NodeDataChildren =
  | {
      type: Exclude<NodeDataChildType, NodeDataChildType.CHOICES>;
    }
  | {
      type: NodeDataChildType.CHOICES;
      choices?: Array<NodeDataChildChoice>;
    };

export enum NodeDataChildType {
  CHOICES = "choices",
  TEXT = "text",
  CONTINUE = "continue",
  NONE = "none",
}
export type NodeDataChildChoice = {
  id: string;
};

Note: This is not built in Node type, but a custom Node Type

What am I doing wrong here?

CodePudding user response:

I think you're just hitting one of the many limits of automatic type narrowing. I think the primary problem is that your code is narrowing the type of node.data.children, but then just copying node. Since node's type hasn't been narrowed by a type guard, newNode just gets the plain Node type — and that type has a union for data.children. You know (and we know) that node and newNode share the same data object, and thus the same data.children object, but TypeScript doesn't go that far.

When I run into a limit like that, I like to use a type assertion function, for example:

function assertIsChoicesNodeData(children: NodeDataChildren): asserts children is NodeDataChildrenWithChoices {
    if ((children as any).type !== NodeDataChildType.CHOICES) {
        throw new Error("Expected a children object with `type` = `NodeDataChildType.CHOICES`");
    }
}

Type assertions are a Bad Thing™ most of the time, but a type assertion function is a different beast because it doesn't just trust the programmer, it verifies the assertion at runtime.

Here's how you'd use it:

if (node.data.children.type === NodeDataChildType.CHOICES && node.id === nodeId) {
    const newNode = { ...node };
    assertIsChoicesNodeData(newNode.data.children);
    console.log(node.data.children.choices, newNode.data.children.choices);
}

Playground link

  • Related