Every of our database tables has an identifier (id) and version. Which means every concrete Entity extends from the Entity interface in order to have those two attributes.
interface Entity {
id: number;
version: number;
}
Here is a multi-level entity for example purposes.
interface Address extends Entity {
street: string;
hno: string;
zip: string;
city: string;
country: string;
}
interface Department extends Entity {
name: string;
}
interface User extends Entity {
name: string;
birthday: string;
address: Address;
department: Department;
}
Now I want a generic type which replaces all appearances of id and version (even the fields in the sub-objects) into optional. Which means from
User {
dbid: number;
version: number;
name: string;
birthday: string;
address: {
dbid: number;
version: number;
street: string;
hno: string;
zip: string;
city: string;
country: string;
};
department: {
dbid: number;
version: number;
name: string;
};
}
into
User {
dbid?: number;
version?: number;
name: string;
birthday: string;
address: {
dbid?: number;
version?: number;
street: string;
hno: string;
zip: string;
city: string;
country: string;
};
department: {
dbid?: number;
version?: number;
name: string;
};
}
As I said as a generic type, because I have a lot of different entities where I want to perform the same.
Something like
export type NewEntity<T extends Entity> = {
// remove id and version fields
[K in keyof Omit<T, 'dbid' | 'version'>]: T[K] extends Entity ? NewEntity<T[K]> : T[K];
};
but this removes only the two attributes but lacks the redefinition of
id?: number;
version?: number
CodePudding user response:
You can define a recursive generic type like this one:
type NewEntity<T extends Entity> = ({
[K in Exclude<keyof T, keyof Entity>]:
T[K] extends Entity
? NewEntity<T[K]>
: T[K]
} & {
[K in Extract<keyof T, keyof Entity>]?: T[K]
}) extends infer O ? { [K in keyof O]: O[K] } : never
It creates an intersection of two mapped types.
The first one is including all properties which are not in Entity
(id
and version
). If a property type T[K]
extends Entity
, we can call the type NewEntity
recursively with it.
The second type includes the properties of T
which are also in Entity
. Those are made optional.
The extends infer O ? { [K in keyof O]: O[K] } : never
is only for aesthetic reasons and makes the resulting type readable. (See this for more information)
Here is the result:
type NewUser = NewEntity<User>
// type NewUser = {
// name: string;
// birthday: string;
// address: {
// street: string;
// hno: string;
// zip: string;
// city: string;
// country: string;
// id?: number | undefined;
// version?: number | undefined;
// };
// department: {
// name: string;
// id?: number | undefined;
// version?: number | undefined;
// };
// id?: number | undefined;
// version?: number | undefined;
// }