I have an object type; I want to get a new tuple type like: [property, value], when I input the property, them typescript can infer the type of value automatically, but I can't solve the problem.
Example:
type DoubleTuple<T extends Record<any, any>> = T extends Record<infer P, any>
? [P, T[P]]
: never;
interface ExampleModel {
a: number;
b: string;
}
// when i input the property 'a', number | string was inferred , but number expected
const tuple: DoubleTuple<ExampleModel> = ["a", "3"];
You want DoubleTuple<T>
type to be a union of entry tuple types for each key-value pair in T
. So DoubleTuple<{a: A, b: B, c: C}>
should be ["a", A] | ["b", B] | ["c", C]
. But your version doesn't do that at all.
First, T extends Record<infer P, any> ? P : never
is equivalent to just keyof T
(unless T
is a union, but that doesn't seem to be your use case), so you can collapse your type to [keyof T, T[keyof T]]
, which mixes all the keys and properties together. Really you want to distribute your operation over the elements of keyof T
, so that it looks like [P, T[P]]
for each P
in keyof T
.
There are different ways to accomplish this but a common one is to build a distributive object type as coined in microsoft/TypeScript#47109. It's of the form {[P in K]: F<P>}[K]
where K
is some union of keylike types (such as keyof T
). This produces a mapped type into which you immediately index to get the union of F<P>
for each P
in K
.
That gives us the following:
type DoubleTuple<T extends Record<any, any>> =
{ [P in keyof T]: [P, T[P]] }[keyof T];
Let's test it out:
interface ExampleModel {
a: number;
b: string;
}
type DTEM = DoubleTuple<ExampleModel>;
// type DTEM = ["a", number] | ["b", string]
let tuple: DoubleTuple<ExampleModel>;
tuple = ["a", 2]; // okay
tuple = ["a", "x"]; // error
tuple = ["b", "x"]; // okay
tuple = ["b", 2]; // error
Looks good.