Home > front end >  Get typed object keys in Typescript
Get typed object keys in Typescript

Time:09-30

I have the next situation:

const data: Record<string, string> = {
    a: '110px',
    b: '160px',
};


interface Props {
    d?: keyof typeof data[];
}


const t = (d: Props) => 'hi'   d;

t(['a']) // here should be allowed only data keys (a and b)

I want to restrict the t() arguments only to data object keys. I tried as above but i get TS error: Type 'string[]' has no properties in common with type 'Props'.(2559). How to achieve that?
NOTE: i need to do this dynamycly reading the keys of the object.
demo: link

CodePudding user response:

Two answers for you:

  1. If by "dynamically" you mean at runtime, no, that's not possible with TypeScript. TypeScript only works with compile-time information.

  2. If by "dynamically" you mean it works even if you change the object literal that creates data, you can do it with (keyof typeof data)[] and allowing TypeScript to infer the type of data (by removing the type annotation on it):

    const data = { // <== Note no `Record<string, string>` on this
        a: '110px',
        b: '160px',
    };
    
    const t = (d: (keyof typeof data)[]) => 'hi'   d;
    t(["a", "b"]); // <=== Works as expected
    t(["x"]); // <=== Error as desired
    

    Playground link

    If you change the object literal defining data so that it (now) has a c property, the definition of t doesn't have to change (playground), it picks that up.

    As captain-yossarian from Ukraine pointed out in a comment, starting with TypeScript 4.9, if you want to make sure that you can't accidentally add an entry to data that doesn't have a string value (or even a more specific one, more in a minute), you can use the new satisfies operator. That lets you enforce a limitation on the object's properties without broadening its type, like this:

    const data = {
        a: '110px',
        b: '160px',
    } satisfies Record<string, string>;
    //^^^^^^^^^−−−−−− new TypeScript 4.9 operator
    
    const t = (d: (keyof typeof data)[]) => 'hi'   d;
    t(["a", "b"]); // <=== Works as expected
    t(["x"]); // <=== Error as desired
    

    When you do that, if you try to add c: 42 to data, you'll get an error from TypeScript (link) because that would mean that data's type didn't satisfy the constraint Record<string, string>. But unlike as, it won't narrow the type of data, so our keyof typeof data still sees only data's actual property names as a union ("a" | "b"), not just string.

    As he also pointed out, if you want to make sure all of your values in data are in the form <number>px, you can use a template literal type like Record<string, `${number}px`> with satisfies (link).

  • Related