Let's say we have the following object type:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Obj = {
[K in "prop_a" | "prop_b" | "prop_c"]: {
[P in `${K}${Digit}`]: unknown;
}
}
By iterating on "prop_a" | "prop_b" | "prop_c"
with K
we can refer directly to each constituent of the union to impose the K-th
prop as a prefix of each inner property of the corresponding sub-object.
The question is: is it possible to get a similar result when the properties of the first level are not known? If yes, how?
A trivial but incorrect attempt is the following:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Obj = {
[K in string]: {
[P in `${K}${Digit}`]: unknown;
}
}
Because the prefix of an inner property is not constrained to the name of the corresponding key on the first level:
declare const obj: Obj;
// should error out because the prefix previous to the digit is not "whatever"
obj.whatever.it_should_error0;
// just the following should be accepted if the first level key is "whatever"
obj.whatever.whatever0;
obj.whatever.whatever1;
obj.whatever.whatever2;
obj.whatever.whatever3;
obj.whatever.whatever4;
obj.whatever.whatever5;
obj.whatever.whatever6;
obj.whatever.whatever7;
obj.whatever.whatever8;
obj.whatever.whatever9;
CodePudding user response:
No, there is no specific type in TypeScript that works this way. Mapping over string
doesn't behave the way you want; it results in a string
index signature that does not keep track of the particular key. Support for a type like you are describing was explicitly declined in microsoft/TypeScript#22509, so I don't think this will be possible in the foreseeable future.
You could possibly write Obj
as a generic constraint:
type Obj<K extends string> = { [P in K]: { [Q in `${P}${Digit}`]: unknown } };
declare const obj: Obj<"whatever" | "otherThing">;
obj.whatever.whatever0 // okay
obj.whatever.it_should_error0; // error
You may not know the full key set at compile time, but if you know any keys you should be able to represent it this way (Obj<KnownKeys | UnknownKeys>
can be safely widened/upcast to Obj<KnownKeys>
, just like {foo: string, bar: number}
can be safely widened to {foo: string}
).
Otherwise the only approach I can think of which approximates what you're describing would be to refactor to use amethod instead of property access:
interface Obj {
get<K extends string>(k: K): { [P in `${K}${Digit}`]: unknown };
}
declare const obj: Obj;
const objWhatever = obj.get("whatever");
objWhatever.whatever0; // okay
objWhatever.it_should_error0; // error