Если вы в порядке с использованием кортежа пути, тогда нам нужно манипулировать кортежами. Полезный псевдоним типа - Tail<T>
, который принимает тип кортежа, например [string, number, boolean]
, и возвращает другой кортеж с удаленным первым элементом, например [number, boolean]
:
type Tail<T extends any[]> =
((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;
. Затем мы можем написать DeepRecord<K, V>
, где K
- это путь ключей свойств, а V
- это некоторое значение. Мы создаем рекурсивный сопоставленный условный тип, который создает тип объекта, в котором V
расположен вниз по пути, описанному K
:
type DeepRecord<K extends PropertyKey[], V> =
K extends [] ? V : { [P in K[0]]: DeepRecord<Tail<K>, V> };
Просто чтобы убедиться, что это работает, вот пример "
type Example = DeepRecord<["foo", "bar", "baz"], string>;
/* type Example = { foo: { bar: { baz: string; }; };} */
Теперь мы, по сути, закончили, но также неплохо сделать что-то, что рекурсивно объединяет пересечения, чтобы вместо {foo: {bar: {baz: string}}} & {foo: {bar: {qux: number}}}
вы получили один {foo: {bar: {baz: string; qux: number;}}}
:
type MergeIntersection<T> =
T extends object ? { [K in keyof T]: MergeIntersection<T[K]> } : T;
Наконец , мы можем дать merge()
сигнатуру типа (и реализацию, хотя это выходит за рамки вопроса и не гарантированно будет правильным):
const merge = <T extends object, K extends N[] | [],
V extends object, N extends PropertyKey>(
src: T, path: K, newObj: V
) => {
const ret = { ...src } as MergeIntersection<T & DeepRecord<K, V>>;
let obj: any = ret;
for (let k of path) {
if (!(k in obj)) {
obj[k] = {};
}
obj = obj[k];
}
Object.assign(obj, newObj);
return ret;
}
Обратите внимание, что N
на самом деле не делает в определении, но это позволяет компилятору сделать вывод, что параметр path
содержит литералов , а не только string
. А | []
в ограничении для K
помогает компилятору сделать вывод кортеж вместо массива. Вы хотите, чтобы ["user", "address"]
выводился как тип ["user", "address"]
вместо string[]
, или все это разваливается. Эти раздражающие волхвы c - это топи c из м icrosoft / TypeScript # 30680 и на данный момент это лучшее, что я могу сделать.
Вы можете проверить это на своем примере кода:
const newObj = merge(User, ["user", "address"], mergeObj);
/* const newObj: {
user: {
address: {
street: string;
door: number;
};
};
}*/
console.log(JSON.stringify(newObj));
// {"user":{"address":{"street":"New Street","door":59}}}
Выглядит хорошо, я думаю. Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код