Is it possible to implicitly state the possible options (based on the key) for the name property in the example below:
type TypeMap = {
A: 1 | 2 | 3;
B: 4 | 5 | 6;
};
type InnerType<TKey extends keyof TypeMap> = {
key: TKey;
name: TypeMap[TKey];
// + other properties;
};
type OuterType = {
x: InnerType; // Should be agnostic at this point
y: InnerType; // but typescript requires a generic definition
};
const foo: OuterType = {
x: { key: "A", name: 2 },
y: { key: "B", name: 4 },
};
The closest I can come up with would be something like the following, (but I would rather not have to change the format of the respective types):
type TypeMap = {
A: 1 | 2 | 3;
B: 4 | 5 | 6;
};
type InnerType = {
data: Partial<{ [key in keyof TypeMap]: TypeMap[key] }>;
// + other properties
};
type OuterType = {
x: InnerType;
y: InnerType;
};
const foo: OuterType = {
x: { data: { A: 2 } }, // not blocking me from using `{}` or `{ A: 2, B: 4 }` on the same object
y: { data: { B: 4 } },
};
Another approach would be the following (but does not limit based on the key):
type TypeMap = {
A: 1 | 2 | 3;
B: 4 | 5 | 6;
};
type InnerType = {
key: keyof TypeMap;
name: TypeMap[keyof TypeMap];
// + other properties;
};
type OuterType = {
x: InnerType;
y: InnerType;
};
const foo: OuterType = {
x: { key: "A", name: 2 }, // allows me to use `{ key: "A", name 4 }`
y: { key: "B", name: 4 },
};
Your version of
InnerTypeis a single object type whose properties are unions, which allows mixing ofkeyandnamein a way you don't want. You really wantInnerTypeto itself be a union of object types, like this:That gives you the behavior you want:
You can even write
InnerTypein terms ofTypeMapso that it will automatically update ifTypeMapis updated:This
InnerTypeis a distributive object type as coined in microsoft/TypeScript#47109. It works by mapping over the keysKofTypeMap, computing the desired object type for each keyK, and then indexing into the mapped type withkeyof TypeMap, resulting in the desired union.If for some reason you want to keep
InnerTypegeneric so thatInnerType<K>corresponds to a particularKconstrained tokeyof TypeMap, you can do it:And note that
Kdefaults tokeyof TypeMap, so thatInnerTypewithout a generic type argument is equivalent to the full union:So you can get both generic-like and union-like behavior depending on your needs.
Playground link to code