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

How can I get the correct type if I access an object property with a string in TypeScript?

Say I have the following code:

interface Response { 'property.a': number; 'property.b': string }
const response: Response = {
  'property.a': 10,
  'property.b': 'foo'
}

I want to access the properties of response in a type-safe way, but it doesn’t completely work as I would expect. What I am looking for is a way of accessing object properties with strings, where

  1. If the object has the property, accessing it gives the correct type
  2. If the object does not have the property, accessing it throws an error

If you access the properties directly, this is what happens:

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

response['property.a'] // works as expected: is of type number
response['property.c'] // should throw an error, but doesn't

So condition 1 is satisfied, but condition 2 isn’t. I also tried the following:

const get = <T>(obj: T, key: keyof T) => obj[key]
get(response, 'property.a') // should be of type number, but is number | string;
get(response, 'property.c') // works as expected: throws error

So at least throws the error, but now it returns a union of all possible property types in Response– this method fails on condition 1, but satisfies condition 2.

Is there a method that satisfies both conditions?

EDIT:

I just figured out that there is a TypeScript rule called noImplicitAny that enabled this kind of behavior! Unfortunately I can’t enable this rule in the codebase I’m currently working in, so I’ll keep this question open in case there’s another workaround.

>Solution :

For direct property access you should definitely get an error unless you don’t have the --noImplicitAny compiler option enabled, which is part of the "standard" --strict suite of compiler options. It is almost universally recommended to enable --strict.


Taking about your get() function method:

Since key is of type keyof T, then obj[key] will be of the indexed access type T[keyof T], which is going to be a union of all known property types in T.

If you want to keep track of the literal type of the specific key passed in as the key parameter, then your get() function needs to be generic in that type, like this:

const get = <T, K extends keyof T>(obj: T, key: K) => obj[key]

Now key is of generic type K constrained to keyof T, and obj[key] is inferred to be of the indexed access type T[K]. Let’s test it:

const num = get(response, 'property.a');
// const num: number

Here, T is inferred to be Response and K is inferred to be "property.a", and so T[K] is number, as desired.

Playground link to code

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