Supposed I have the following interface:
interface Person {
name: string;
age: number;
location: string;
}
I want to extend it to have 3 additional properties, one for each of the interfaces' original properties.
So final outcome I want is:
interface Person {
name: string;
nameHidden: boolean;
age: number;
ageHidden: boolean;
location: string;
locationHidden: boolean;
}
I was looking into mapped types by TypeScript: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
But I am unable to figure out how I can achieve this behavior. It only shows examples of how to re-map the existing properties, but not add new ones on top of the existing ones.
CodePudding user response:
You've said you don't need it to be the same interface. If so, you can do this:
type WithFlags<T> = T & {
[Key in keyof T as Key extends string ? `${Key}Hidden` : never]: boolean;
};
type PersonWithFlags = WithFlags<Person>;
That creates a new type that is the passed-in type T
plus a property for every property in it (at least the ones whose names extend string
) with Hidden
added to the name and the type boolean
.
Titian Cernicova-Dragomir points out in a comment (thanks!) that we can avoid the conditional in that key mapping by using & string
instead, which is a bit shorter:
type WithFlags<T> = T & {
[Key in keyof T & string as `${Key}Hidden`]: boolean;
// −−−−−−−−−−−−−−−−^^^^^^^^^
};
pilchard points out in a comment that the documentation has virtually this same example. They do the remapping like Titian, but in a different place:
type WithFlags<T> = T & {
[Key in keyof T as `${Key & string}Hidden`]: boolean;
// −−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^
};
CodePudding user response:
I took T.J. Crowder
's answer and modified it a little to make the type a little more (re)usable:
type WithFlags<T, TFlag extends string, TValue = null> = T & {
[Key in keyof T & string as `${Key}${TFlag}`]: TValue extends null ? T[Key] : TValue;
};
By default, you have to pass the interface and the flag name, which results in the following type based on the Person
interface and TFlag
= Hidden
:
{
name: string;
age: number;
location: string;
nameHidden: string;
ageHidden: number;
locationHidden: string;
}
If you want the additional properties to be of a specific type like boolean
you can adjust the 3rd type parameter TValue
.
To fulfill your requirements, you can have a helper type like:
type WithHiddenFlags<T> = WithFlags<T, 'Hidden', boolean>;
and for WithHiddenFlags<Person>
the resulting type is:
{
name: string;
age: number;
location: string;
nameHidden: boolean;
ageHidden: boolean;
locationHidden: boolean;
}
The only limitation that I see is that for TValue = null
, the resulting properties will have the original properties' type instead of null
.