Sorry for unclear title but I have no clue on how to describe my question shortly.
I have some type with one literal value and object with flat structure of this types as values (simplified version):
type SomeType<T = string> = { value: T };
type SomeTypeMapped = Record<string, SomeType>;
I want to write helper (generic) to tell TS that
const a = helper({
A: { value: 'literal-1' },
B: { value: 'literal-2' }
})
has a type of:
const a: {
A: SomeType<"literal-1">;
B: SomeType<"literal-2">;
}
So far the closest solution I found is
function helper<V extends string, K extends string>(val: { [key in K]: SomeType<V> }) {
return val;
}
const a = helper({
A: { value: 'asd' }.
B: { value: 'qwe' }
});
// -->
const a: {
A: SomeType<"asd" | "qwe">;
B: SomeType<"asd" | "qwe">;
}
If I try to modify helper as so:
function helper<
V extends string,
K extends string,
T extends { [key in K]: SomeType<V> }
>(val: T) {
return val;
}
it converts the whole map to constant literal type:
const a: {
A: {
value: "asd";
};
B: {
value: "qwe";
};
}
which is also close to what I'm trying to achieve, but my second point is readability, as
const a: {
A: SomeType<"asd">;
B: SomeType<"qwe">;
}
is much more readable for more complicated examples (with more fields than single 'value').
Can I somehow tell TS that field A
has exactly type of SomeType<"asd">
as well as field B
has type of SomeType<"qwe">
?
CodePudding user response:
Here is how I'd write helper()
:
const helper =
<T extends Record<keyof T, string>>(val: { [K in keyof T]: SomeType<T[K]> }) => val;
The generic type parameter T
is a mapping from the keys of val
to the associated string
literal types you want to pass into SomeType
. So the intent is that when you call
const a = helper({
A: { value: 'literal-1' },
B: { value: 'literal-2' }
})
T
will be inferred as { A: "literal-1"; B: "literal-2" }
. Then val
is not of type T
directly, but of the mapped type {[K in keyof T]: SomeType<T[K]>}
.
Note that the compiler is indeed able to infer the type T
from a value of a homomorphic mapped type of the form {[K in keyof T]: ...T[K]...}
. It's not really well-documented in the current version of the TS Handbook, but there is a section of the now-deprecated v1 of the Handbook about inference from mapped types that describes this.
Anyway, that means when you call helper()
above, T
is inferred properly, and val
is inferred to be of this type:
/* const a: {
A: SomeType<"literal-1">;
B: SomeType<"literal-2">;
} */
as desired.