Antonio Santoro
Antonio Santoro

Reputation: 267

Create a type having the keys from the values of an Object

Given the following type:

type FromValues<T> = T extends Record<infer K, infer V>
  ? V extends string
    ? { [Key in keyof V]: string }
    : never
  : never

I was expecting to get a type which keys are taken from the type of the passed object of type T like:

const mapping = {
  cluster: 'CLUSTER_DATA_PLATFORM',
  subcluster: 'SOTTOCLUSTER_DATA_PLATFORM',
} as const

So, FromValues should generate types like

{
    CLUSTER_DATA_PLATFORM: string
    SOTTOCLUSTER_DATA_PLATFORM: string
}

However what I get is the following type

"CLUSTER_DATA_PLATFORM" | "SOTTOCLUSTER_DATA_PLATFORM"

What am I doing wrong?

Upvotes: 3

Views: 1646

Answers (1)

jcalz
jcalz

Reputation: 327624

First of all, you don't want [Key in keyof V] because V is a some string-like type; keyof V will be something like "length" | "substring" | ..., all the keys with which you can index into a string. You just want to iterate over the union members of V themselves, like [K in V].

Then when you wrote V extends string ? ... : you were accidentally making a distributive conditional type, which would result in { CLUSTER_DATA_PLATFORM: string } | { SOTTOCLUSTER_DATA_PLATFORM: string } instead of the desired result. You can fix that by wrapping the check in a one-tuple like [V] extends [string] ? ... :. That gives you this:

type FromValues<T> = T extends Record<infer K, infer V>
  ? [V] extends [string] ? { [Key in V]: string } : never
  : never

which works:

type Z = FromValues<typeof mapping>;
/* type Z = {
    CLUSTER_DATA_PLATFORM: string;
    SOTTOCLUSTER_DATA_PLATFORM: string;
} */

But you could simplify to avoid conditional types entirely:

type FromValues<T extends Record<keyof T, string>> =
  { [K in T[keyof T]]: string };

which gives the same result.

Obviously there are edge cases, if you try to pass types to FromValues where some of the value types are not strings, you'll get different behaviors, and you have to figure out what the desired behavior should be and implement something that works that way. That's out of scope of the question as asked, though.

Playground link to code

Upvotes: 2

Related Questions