3

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
};
3
  • 1
    type Buzz<T> = {[K in keyof T]-?: {key: K; formatter: (d: T[K])=>void}}[keyof T] Commented Jun 30, 2019 at 23:20
  • @jcalz why just a comment, seems like a reasonable answer Commented Jul 1, 2019 at 8:12
  • @TitianCernicova-Dragomir I don't know, I didn't have time to write a full answer. I suppose I thought someone else might answer but I can see if it looks like I'm "squatting" on a question, which wasn't my intent. Commented Jul 1, 2019 at 14:08

1 Answer 1

5

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.