Reputation: 59365
Given a Person
type
type Person = {
name: string,
age: number,
address: {
line1: string,
line2: string | null | number,
zip: string | number,
},
};
I'd like to apply overwrites and modify this interface.
type PersonOverwrites = {
address: {
line2?: string,
zip: string,
},
};
From these two interfaces I would like to create this:
type PersonModified = {
name: string,
age: number,
address: {
line1: string,
line2?: string,
zip: string,
},
}
This is what I am looking for:
type PersonModified = Overwrite<Person, PersonOverwrites>
How can I create this type of Generic?
Update
This should complain:
type DeepMerge<T, U> = [T, U] extends [object, object] ?
{
[K in keyof (U & Pick<T, Exclude<keyof T, keyof U>>)]: (
K extends keyof U ? (
K extends keyof T ? DeepMerge<T[K], U[K]> : U[K]
) : (
K extends keyof T ? T[K] : never
)
)
} : U;
type Person = {
name: string,
age: number,
address: {
line1: string,
line2: string | null | number,
zip: string | number,
address?: {
line1: string,
line2: string | null | number,
zip: string | number,
address?: {
line1: string,
line2: string | null | number,
zip: string | number,
},
},
},
};
type PersonOverwrites = {
address: {
line2?: string,
zip: string,
address?: {
address: {
pizzaDelivery: boolean,
},
},
},
};
const person: Person = {
name: 'Thomas',
age: 12,
address: {
line1: 'hi',
line2: 'hi',
zip: 'hi',
address: {
line1: 'hi',
line2: 'hi',
zip: 'hi',
address: {
line1: 'hi',
line2: 'hi',
zip: 'hi',
// pizzaDelivery: true,
}
}
}
}
Upvotes: 1
Views: 37
Reputation: 59365
Building of @jcalz answer I believe this fixes the optional issue.
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;
type DeepMerge<T, U> = [T, U] extends [object, object] ?
{
[K in keyof (Merge<T, U>)]: (
K extends keyof U ? (
K extends keyof T ? DeepMerge<T[K], U[K]> : U[K]
) : (
K extends keyof T ? T[K] : never
)
)
} : Merge<T, U>;
Upvotes: 0
Reputation: 328262
I don't know how deeply nested you want this to be, but you might want something like this:
type DeepMerge<T, U> = [T, U] extends [object, object] ?
{
[K in keyof (U & Pick<T, Exclude<keyof T, keyof U>>)]: (
K extends keyof U ? (
K extends keyof T ? DeepMerge<T[K], U[K]> : U[K]
) : (
K extends keyof T ? T[K] : never
)
)
} : U;
type PersonModified = DeepMerge<Person, PersonOverwrites>
The idea is that DeepMerge<T, U>
either evaluates to just U
if either T
or U
is not an object type, or it walks through each combined key of T
or U
and merges more deeply. The specifics around how it determines which properties should be optional, and how it decides when to stop merging are straightforward but tedious to explain. If you need something specific elaborated, let me know.
You can verify that PersonModified
is equivalent to
type PersonModified = {
address: {
line2?: string | undefined;
zip: string;
line1: string;
};
name: string;
age: number;
}
as you wanted.
Hope that helps; good luck!
Upvotes: 3