summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts/merge.ts
blob: 4e39a0fa06ec8b11fc10634eb32fd4e1a02c3801 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
 * SPDX-FileCopyrightText: syuilo and misskey-project
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import { deepClone } from './clone.js';
import type { Cloneable } from './clone.js';

type DeepPartial<T> = {
	[P in keyof T]?: T[P] extends Record<string | number | symbol, unknown> ? DeepPartial<T[P]> : T[P];
};

function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
	return typeof value === 'object' && value !== null && !Array.isArray(value);
}

/**
 * valueにないキーをdefからもらう(再帰的)\
 * nullはそのまま、undefinedはdefの値
 **/
export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: DeepPartial<X>, def: X): X {
	if (isPureObject(value) && isPureObject(def)) {
		const result = deepClone(value as Cloneable) as X;
		for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
			if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
				result[k] = v;
			} else if (isPureObject(v) && isPureObject(result[k])) {
				const child = deepClone(result[k] as Cloneable) as DeepPartial<X[keyof X] & Record<string | number | symbol, unknown>>;
				result[k] = deepMerge<typeof v>(child, v);
			}
		}
		return result;
	}
	throw new Error('deepMerge: value and def must be pure objects');
}