Search code examples
typescripttypescript-types

How can I keep the original index signature of an object, but still specify the type of its values?


I have a large object with string keys and typed values. I want to type this object.

interface Animal {
    legs: 0 | 1 | 2 | 3 | 4;
}

const Animals: Record<string, Animal> = {
    snake: { legs: 0 },
    dog: { legs: 4 },
    bird: { legs: 2},
    // many more entries...
};

If I type the object as Record<string, Animal>, then all strings become valid keys. So there is no protection against me accidentally writing Animals.birb. It would be silly to do something like Record<"snake" | "dog" | "bird" ... , Animal> because the object is big and has many keys.

If I do not explicitly write a type definition, then TypeScript will infer the type as { snake: { legs: number}, dog: { legs: number}, ... }.

This is bad because I need TypeScript to know that the type of value is Animal and not just { legs: number },

i.e., the type should be { snake: Animal, dog: Animal, ... }

Writing as Animal for every line would be silly.

Is there a better way to type this object?


Solution

  • If you want to ensure that a variable is assignable to a type without annotating it as that type and potentially throwing away information (like the particular keys), you can use the satisfies operator:

    const Animals = {
        snake: { legs: 0 },
        dog: { legs: 4 },
        bird: { legs: 2 },
    } satisfies Record<string, Animal>;
    
    /* const Animals: {
        snake: {
            legs: 0;
        };
        dog: {
            legs: 4;
        };
        bird: {
            legs: 2;
        };
    } */
    

    You can see that Animals has been inferred with known keys, and with properties whose legs are of appropriately narrow literal types instead of the wider number type which would have been inferred otherwise.

    This also will catch mistakes in your initializer similarly to an annotation:

    const badAnimals = {
        snake: { legs: 0 },
        dog: { legs: 4 },
        bird: { legs: 2 },
        octopus: { legs: 8 } // error!
    } satisfies Record<string, Animal>;
    

    Playground link to code