user3690467
user3690467

Reputation: 3387

Typescript - Detect value types based on the object key provided

Given I have a type like:

type Foo = {
  foo: number;
  bar: string;
  baz: boolean;
}

I want to have a type Buzz which can detect the value type against a key, i.e

const abc: Buzz<Foo> = {
  key: 'foo',
  formatter: (detectMe) => {} //I want TS to infer this to be 'number'
};

Given the key 'foo' the argument in formatter should be inferred to be number. I tried this:

interface ColumnDescription<T> {
  label: string;
  key: keyof T;
  formatter?: (datum: T[keyof T]) => void;
}

However this results in the argument being inferred to be number | string | boolean.

Tried this as well:

interface ColumnDescription<T, K extends keyof T> {
  label: string;
  key: K;
  formatter?: (datum: T[K]) => void;
}

This sort of works but I always need the specify the key in the second type argument, instead of it happening automatically. i.e:

const abc: Buzz<Foo, 'foo'> = { //I don't want to specify the key
  key: 'foo',
  formatter: (detectMe) => {} //This is inferred correctly
};

Upvotes: 3

Views: 1874

Answers (1)

jcalz
jcalz

Reputation: 328272

As in my comment, I'd suggest

type Buzz<T> = {
  [K in keyof T]-?: { key: K; formatter: (d: T[K]) => void }
}[keyof T];

which is similar to what you did with Buzz<T, K extends keyof T>, but instead of making Buzz need K to be specified, I used a mapped type {[K in keyof T]: ...} which automatically iterates over keys in keyof T and makes a new object with the same keys but whose property values are the types you're looking for. That means to get the desired Buzz<T> we need to look up the property values, by indexing into it with [keyof T]. That makes Buzz<T> a union of types, where each constituent of the union corresponds to your Buzz<T, K extends keyof T> for a particular key K

Let's make sure it works:

const abc: Buzz<Foo> = {
  key: "foo",
  formatter: detectMe => {} // inferred as number, as desired
};

Looks good, and let's inspect the type of abc with IntelliSense:

const abc: {
    key: "foo";
    formatter: (d: number) => void;
} | {
    key: "bar";
    formatter: (d: string) => void;
} | {
    key: "baz";
    formatter: (d: boolean) => void;
}

That looks good too.

Okay, hope that helps; good luck!

Link to code

Upvotes: 5

Related Questions