Accept keyof of an object with a specific type?

Viewed 54

I have this type (shortened here for example):

type Form = {
    firstName: string,
    lastName: string,
    age: number
};

I want to create two functions, one which accepts any key of Form where the value is of type string, and one for number. So the first one should accept "firstName" | "lastName" and the second one "age"

I also want to be able to index the object with the key and get back the correct type. E.g. for the string function:

function stringKey<... magic ...>(form: Form, key: ...) {
  form[key] // should be of type string
}

I made a guess and tried this but it's invalid syntax. Is it possible to do this in TypeScript?

function stringKey<K extends keyof Form, Form[K] extends string>()
2 Answers

Yes it is totally possible

First of all you need to pick all props of corresponding type from object

type PickByType<OBJ extends object, VALUE_TYPE> = Pick<
  OBJ, 
  {
    [KEY in keyof OBJ]-?: OBJ[KEY] extends VALUE_TYPE
      ? KEY // Keep key/value pair if value is of VALUE_TYPE
      : never // Brush it off otherwise
  }[keyof OBJ] // Meaning we take every key/value pair from object above and combine them with union type
>

Once you have it, you need to pick all keys from this type, I think you are able to do it yourself :)

Resulting function you got almost right

Try to get to the answer yourself with this hint above. After that, feel free to check out complete solution

First you need a way to get only the property names that are typed as string. You can use a mapped type to do that. Iterate over each key and find the ones that have string as their type.

type StringKeys<T> = {
  [K in keyof T]: T[K] extends string ? K : never
}[keyof T]

// Test it out
type FormPropertiesThatAreString = StringKeys<Form> // 'firstName' | 'lastName'

Now you can write your function with a single generic parameter for that key, constraining it to StringKeys<Form>.

function stringKey<K extends StringKeys<Form>>(form: Form, key: K) {
  const shouldBeAString: string = form[key] // type: string
  const shouldBeAnError: number = form[key] // type error
}

Playground

Related