Append to Object
Challenge
Implement a type that adds a new field to the interface. The type takes the three arguments. The output should be an object with the new field.
For example:
type Test = { id: "1" };
type Result = AppendToObject<Test, "value", 4>; // expected to be { id: '1', value: 4 }
Solution
When we try to change objects\interfaces in TypeScript, usually intersection
types are helpful for that. This challenge is not an exception. I’ve tried to
write a type that takes the whole T
plus an object with a new property:
type AppendToObject<T, U, V> = T & { [P in U]: V };
Unfortunately, this solution does not satisfy the tests. They are expecting to
have a flat type, not the intersection. So we need to return an object type
where all the properties plus our new property. I’ll start by mapping the
properties from T
:
type AppendToObject<T, U, V> = { [P in keyof T]: T[P] };
Now, we need to add to those properties of T
our new property U
. Easy, here
is the trick. Nothing stops you from passing a union to in
operator:
type AppendToObject<T, U, V> = { [P in keyof T | U]: T[P] };
That way, we will get all the properties of T
plus properties of U
, exactly
what we need here. Let us fix minor errors now by adding a constraint to U
:
type AppendToObject<T, U extends string, V> = { [P in keyof T | U]: T[P] };
The only thing that TypeScript can’t handle now is the fact, that P
can be
absent in T
, because P
is a union of T
and U
. We need to handle the case
and check, if P
is from the T
, we get T[P]
, otherwise we pass V
:
type AppendToObject<T, U extends string, V> = {
[P in keyof T | U]: P extends keyof T ? T[P] : V;
};
Comments