Я интерпретирую то, что вы запрашиваете, следующим образом: для типов объектов T
и U
тип объекта SafelyMergedObject<T, U>
должен иметь те же ключи, что и T & U
, но с некоторыми отличиями от свойства типы. Если ключ K
существует только в T
или U
, но не в обоих, используйте свойство как есть (так что это то же самое, что и T & U
). Если ключ K
существует в T
и U
и хотя бы один из двух типов свойств не является объектом, используйте тип свойства из T
и игнорируйте свойство из U
. Если ключ K
существует в T
и U
, а является типом объекта в T
и U
, затем вернитесь в это свойство с помощью SafelyMergedObject<T[K], U[K]>
.
Это переводится в нечто вроде:
type SafelyMergedObject<T, U> = (
Omit<U, keyof T> & { [K in keyof T]:
K extends keyof U ? (
[U[K], T[K]] extends [object, object] ?
SafelyMergedObject<T[K], U[K]>
: T[K]
) : T[K] }
) extends infer O ? { [K in keyof O]: O[K] } : never;
Здесь мы сначала выводим Omit<U, keyof T>
, то есть свойства из U, которые не существуют в T
. Затем мы проходим ключи T
и выводим T[K]
, если свойство не в U
, или если оно находится в U
, но хотя бы один из T[K]
или U[K]
не является объект.
Единственный "трюк" здесь - extends infer O ? {[K in keyof O]: O[K]} : never
. Все, что это делает, - это «предварительно укрепляет» или «расширяет» тип объекта, перебирая все ключи и объединяя результат в один тип объекта.
Давайте посмотрим на это в действии с вашими src
и ext
значения:
type Result = SafelyMergedObject<typeof src, typeof ext>;
Если вы наводите курсор на это с IntelliSense, вы увидите:
type Result = {
e: string;
a: string;
b: string;
c: boolean;
d: {
c: string;
a: string;
b: string;
};
}
, что, я думаю, то, что вы хотели. Обратите внимание, что если бы я не включил строку extends infer O...
, тип Result
был бы оценен как:
type Result = Pick<{
a: string[];
b: number;
c: boolean;
d: {
a: string;
c: string;
};
e: string;
}, "e"> & {
a: string;
b: string;
c: boolean;
d: SafelyMergedObject<{
a: string;
b: string;
}, {
a: string;
c: string;
}>;
}
, что значительно сложнее для понимания, хотя это тот же тип.
Обратите внимание, что, вероятно, существуют всевозможные крайние случаи, которые возникнут, если вы используете выше SafelyMergedObject<T, U>
в различных ситуациях. Вам нужно будет решить, как вы хотите, чтобы результат выглядел в этих ситуациях, и, возможно, изменить определение, чтобы это произошло. Так что будьте осторожны.
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код