Home > Software design >  TypeScript: use a nested, constant object definition with a type for inferred type definition
TypeScript: use a nested, constant object definition with a type for inferred type definition

Time:01-07

I have a static object definition that I would like to ensure conforms to a type but also be able to use the constant definition for inferred types. For example:

const TREE_BRANCH_TYPES = ['Type1', 'Type2'] as const;

type TreeBranchType = typeof TREE_BRANCH_TYPES[number];
type TreeRoot = Record<TreeBranchType, TreeBranch>;
type TreeBranch = { leaves: readonly TreeLeaf[] };
type TreeLeaf = { name: string };

const tree: TreeRoot = {
    Type1: {
        leaves: [
            { name: 'Type1-Leaf1' },
            { name: 'Type1-Leaf2' },
        ]
    },
    Type2: {
        leaves: [
            { name: 'Type2-Leaf1' },
            { name: 'Type2-Leaf2' },
        ]
    }
} as const;

type TreeLeafName = typeof tree[TreeBranchType]['leaves'][number]['name'];

function findLeaf(type: TreeBranchType, name: TreeLeafName): TreeLeaf | undefined {
    return tree[type].leaves.find(leaf => leaf.name === name);
}

Given the above, the compiler will require tree to conform to the TreeRoot definition, but TreeLeafName is interpreted by the compiler as type TreeLeafName = string.

However, if I remove the type from tree (i.e. const tree = { ... };), then TreeLeafName is recognized by the compiler as type TreeLeafName = "Type1-Leaf1" | "Type1-Leaf2" | "Type2-Leaf1" | "Type2-Leaf2".

Is there a way to get the best of both worlds? Type safety on the tree constant definition and type safety of the inferred TreeLeafName type?

CodePudding user response:

The easiest way to do this with TS4.9 and above is to use the satisfies operator to check that a value is assignable to a type without widening/upcasting it to that type:

const tree = {
    Type1: {
        leaves: [
            { name: 'Type1-Leaf1' },
            { name: 'Type1-Leaf2' },
        ]
    },
    Type2: {
        leaves: [
            { name: 'Type2-Leaf1' },
            { name: 'Type2-Leaf2' },
        ]
    }
} as const satisfies TreeRoot;

type TreeLeafName = typeof tree[TreeBranchType]['leaves'][number]['name'];
// type TreeLeafName = "Type1-Leaf1" | "Type1-Leaf2" | "Type2-Leaf1" | "Type2-Leaf2"

Playground link to code

  • Related