Reputation: 267
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
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 string
s, 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.
Upvotes: 2