Given
// GIVEN
type Animal<T extends string> = {
id: T,
}
type Dog = Animal<"animal.dog"> & {
foo: string
}
type Cat = Animal<"animal.cat"> & {
bar: string
}
type MyType<T extends Dog | Cat = Dog | Cat> = { [K in T["id"]]: keyof T };
type MyTypeVariant<C extends "animal.dog" | "animal.cat" = "animal.dog" | "animal.cat"> = { [K in C]: keyof Animal<C> };
// EXPECT TO BE OK
const obj: MyType = {
"animal.dog": "foo",
"animal.cat": "bar"
}
// EXPECT TO FAIL
const fail: MyType = {
"animal.dog": "bar",
"animal.cat": "foo"
}
How can I create a type that would raise an error if the value
(ex: "foo") is not a key of Type (ex: Cat), Type that can be deduced from the key
(ex: "animal.cat") of obj?
For now I get the error
Type '"foo"' is not assignable to type '"id"'
Because "id" is the only property in common between Dog
& Cat
.
I guess I would need type inference here.
Any ideas?
The type you're looking for is
type Type =
{ [T in Dog | Cat as T["id"]]: keyof T }
which evaluates to
type Type = {
"animal.dog": "id" | "foo";
"animal.cat": "id" | "bar";
}
This uses key remapping in mapped types to iterate over the union Dog | Cat
, and for each member T
of that union, we use T["id"]
as the key, and keyof T
as the value. Since we're iterating over the members of T
, then the correlation between key and value is preserved.
If we needed to do it your way, where we iterate K
over T["id"]
instead of T
, we'd need to check K
and Extract
the right member of T
, perhaps like this:
type TypeGen<T extends Dog | Cat = Dog | Cat> =
{ [K in T["id"]]: keyof Extract<T, { id: K }> }
type Type = TypeGen
/* type Type = {
"animal.dog": "id" | "foo";
"animal.cat": "id" | "bar";
} */
But key remapping is more straightforward.