Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Get typed object keys in Typescript

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

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).

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading