deepmerge-plus
Version:
用於深度(遞迴)合併 JavaScript 物件的函式庫 / A library for deep (recursive) merging of JavaScript objects
381 lines (377 loc) • 14.6 kB
JavaScript
'use strict';
var isMergeableObject = require('is-mergeable-object');
/**
* 深度合併兩個 JavaScript 物件(可遞迴合併巢狀物件和陣列)
* Deep (recursive) merge of two JavaScript objects
*
* @package deepmerge-plus
* @version 3.0.2
*/
/**
* 建立空目標物件 / Create empty target object
*
* 根據輸入值類型回傳空陣列或空物件
* 用於深度合併時建立目標的空白副本
*
* Returns empty array or object based on input value type
* Used to create a blank copy of target during deep merge
*
* @param val - 輸入值 / Input value (array or object)
* @returns 空陣列(若輸入為陣列)或空物件(若輸入為物件)/ Empty array (if input is array) or empty object (if input is object)
*
* @example
* emptyTarget([1, 2, 3]) // 回傳 []
* emptyTarget({ a: 1 }) // 回傳 {}
*/
function emptyTarget(val) {
return Array.isArray(val) ? [] : {};
}
/**
* 決定是否啟用深度複製功能
* 預設為 true(啟用),除非選項明確設為 false
*
* Determine whether to enable deep cloning
* Default is true (enabled), unless options explicitly set to false
*
* @note 注意事項 / Note:
* 即使 `options.clone` 設為 `false`,在某些情況下仍可能會產生 clone。
* 這是正常現象,因為深度合併需要確保輸出物件與輸入物件相互獨立,
* 以避免意外修改原始資料。
*
* Even if `options.clone` is set to `false`, cloning may still occur
* in certain situations. This is expected behavior because deep merge
* needs to ensure the output object is independent from the input objects
* to prevent accidental modification of original data.
*/
function _shouldClone(optionsRuntime) {
const clone = (optionsRuntime === null || optionsRuntime === void 0 ? void 0 : optionsRuntime.clone) !== false;
return clone;
}
/**
* 除非另有指定,否則複製值 / Clone value unless otherwise specified
*
* 決定是否需要深度複製輸入值:
* 1. 檢查選項中的 clone 設定
* 2. 判斷值是否為可合併物件
* 3. 若需要合併則遞迴複製
*
* Determines whether to deep clone the input value:
* 1. Check clone option in optionsArgument
* 2. Check if value is mergeable object
* 3. Recursively clone if mergeable
*
* @param value - 要複製的值 / Value to clone
* @param optionsRuntime - 合併選項 / Merge options
* @param tmpRuntimeTarget - 內部快取資訊 / Internal cache information
* @returns 複製後的值或原始值 / Cloned value or original value
*/
function cloneUnlessOtherwiseSpecified(value, optionsRuntime, tmpRuntimeTarget, tmpRuntimeData) {
/**
* 決定是否啟用深度複製功能
* 預設為 true(啟用),除非選項明確設為 false
*
* Determine whether to enable deep cloning
* Default is true (enabled), unless options explicitly set to false
*
* @note 即使 `options.clone` 設為 `false`,在某些情況下仍可能會產生 clone
* @note Even if `options.clone` is set to `false`, cloning may still occur
*/
const clone = _shouldClone(optionsRuntime);
const bool = clone && _isMergeableObject(value, optionsRuntime, tmpRuntimeTarget, tmpRuntimeData);
let ret = bool ? deepmerge(emptyTarget(value), value, optionsRuntime, tmpRuntimeData) : value;
if (optionsRuntime !== null && optionsRuntime !== void 0 && optionsRuntime.keyValueOrMode && !bool && tmpRuntimeTarget && 'key' in tmpRuntimeTarget) {
if (tmpRuntimeTarget.destination) {
ret = tmpRuntimeTarget.destination[tmpRuntimeTarget.key] || ret;
}
if (tmpRuntimeTarget.target) {
ret = tmpRuntimeTarget.target[tmpRuntimeTarget.key] || ret;
}
if (tmpRuntimeTarget.source) {
ret = tmpRuntimeTarget.source[tmpRuntimeTarget.key] || ret;
}
}
if (optionsRuntime !== null && optionsRuntime !== void 0 && optionsRuntime.keyValueUpsertMode && !bool && tmpRuntimeTarget && 'key' in tmpRuntimeTarget) {
let shouldNotUpsertValue = false;
if (typeof optionsRuntime.keyValueUpsertMode === 'function') {
shouldNotUpsertValue = optionsRuntime.keyValueUpsertMode(value, optionsRuntime, tmpRuntimeTarget, tmpRuntimeData);
} else if (optionsRuntime.keyValueUpsertMode === true) {
var _tmpRuntimeTarget$tar2;
const targetValue = (_tmpRuntimeTarget$tar2 = tmpRuntimeTarget.target) === null || _tmpRuntimeTarget$tar2 === void 0 ? void 0 : _tmpRuntimeTarget$tar2[tmpRuntimeTarget.key];
shouldNotUpsertValue = !_isUndefined(targetValue);
}
if (shouldNotUpsertValue) {
let upsertValue;
if (tmpRuntimeTarget.destination) {
upsertValue !== null && upsertValue !== void 0 ? upsertValue : upsertValue = tmpRuntimeTarget.destination[tmpRuntimeTarget.key];
}
if (tmpRuntimeTarget.target) {
upsertValue !== null && upsertValue !== void 0 ? upsertValue : upsertValue = tmpRuntimeTarget.target[tmpRuntimeTarget.key];
}
ret = upsertValue;
}
}
return ret;
}
function _isUndefined(value) {
return typeof value === 'undefined';
}
/**
* 檢查值是否為可合併物件 / Check if value is a mergeable object
*
* 判斷邏輯:
* 1. 先檢查選項中的自訂 isMergeableObject 函式
* 2. 若無自訂函式,檢查 SYMBOL_IS_MERGEABLE 符號
* 3. 若無符號,使用預設的 isMergeableObject 函式
*
* Decision logic:
* 1. First check custom isMergeableObject function in options
* 2. If no custom function, check SYMBOL_IS_MERGEABLE symbol
* 3. If no symbol, use default isMergeableObject function
*
* @param value - 要檢查的值 / Value to check
* @param optionsArgument - 合併選項 / Merge options
* @param tmpRuntimeTarget - 內部快取資訊 / Internal cache information
* @returns 是否可合併 / Whether mergeable
*/
function _isMergeableObject(value, optionsArgument, tmpRuntimeTarget, tmpRuntimeData) {
var _optionsArgument$isMe;
let ret = optionsArgument === null || optionsArgument === void 0 || (_optionsArgument$isMe = optionsArgument.isMergeableObject) === null || _optionsArgument$isMe === void 0 ? void 0 : _optionsArgument$isMe.call(optionsArgument, value, isMergeableObject, optionsArgument, tmpRuntimeTarget, tmpRuntimeData);
if (ret === null || typeof ret === 'undefined') {
if (typeof (value === null || value === void 0 ? void 0 : value[SYMBOL_IS_MERGEABLE]) == 'boolean') {
ret = value[SYMBOL_IS_MERGEABLE];
} else {
ret = isMergeableObject(value);
}
}
return ret;
}
function defaultArrayMerge(target, source, optionsArgument, tmpRuntimeData) {
// @ts-ignore
tmpRuntimeData !== null && tmpRuntimeData !== void 0 ? tmpRuntimeData : tmpRuntimeData = _newTmpRuntimeData([]);
const level = tmpRuntimeData.level + 1;
const root = tmpRuntimeData.root;
const parent = [];
tmpRuntimeData.parent[tmpRuntimeData.key] = parent;
return target.concat(source).reduce((parent, element, index) => {
element = cloneUnlessOtherwiseSpecified(element, optionsArgument, {
key: index
}, {
level,
paths: [...tmpRuntimeData.paths, index],
root,
parent,
key: index
});
parent[index] = element;
return parent;
}, parent);
}
/**
* 合併兩個物件 / Merge two objects
*
* 這是 deepmerge 處理物件合併的核心函式
* 負責將來源物件的屬性合併到目標物件
*
* This is the core function for object merging in deepmerge
* Responsible for merging source object properties into target object
*
* 由左至右的概念 / Left-to-right concept:
* merge(target, source, options) 中:
* - 第一個參數 target 是「左側」(left)- 被合併的物件,代表現有值
* - 第二個參數 source 是「右側」(right)- 要合併進來的物件,代表新值
* - 合併結果是將「右側」的值合併進「左側」
*
* In merge(target, source, options):
* - First parameter target is "left" - the object being merged INTO, represents existing values
* - Second parameter source is "right" - the object being merged FROM, represents new values
* - The result merges "right" values INTO "left"
*
* 處理邏輯 / Processing logic:
* 1. 建立目的物件(destination)用於存放合併結果
* 2. 遍歷目標物件的所有鍵值:
* - 複製到目的物件,進行深度複製
* 3. 遍歷來源物件的所有鍵值:
* - 若鍵不存在於目標 OR 來源值不可合併 → 直接複製
* - 若鍵存在於目標 AND 來源值可合併 → 遞迴呼叫 deepmerge 進行深度合併
*
* @param target - 目標物件(被合併的物件)/ Target object (the object being merged into)
* @param source - 來源物件(要合併的物件)/ Source object (the object to merge from)
* @param optionsArgument - 合併選項 / Merge options
* @returns 合併後的新物件 / New merged object
*/
function mergeObject(target, source, optionsArgument, tmpRuntimeData) {
let destination = {};
if (!tmpRuntimeData) {
tmpRuntimeData = _newTmpRuntimeData(destination);
}
if (_isMergeableObject(target, optionsArgument, void 0, tmpRuntimeData)) {
Object.keys(target).forEach(function (key) {
destination[key] = cloneUnlessOtherwiseSpecified(target[key], optionsArgument, {
key,
source,
target,
destination
}, {
level: tmpRuntimeData.level + 1,
paths: [...tmpRuntimeData.paths, key],
root: tmpRuntimeData.root,
parent: destination,
key: key
});
});
}
Object.keys(source).forEach(function (key) {
if (!_isMergeableObject(source[key], optionsArgument, {
key,
source,
target
}, tmpRuntimeData) || !target[key]) {
destination[key] = cloneUnlessOtherwiseSpecified(source[key], optionsArgument, {
key,
source,
target
}, {
level: tmpRuntimeData.level + 1,
paths: [...tmpRuntimeData.paths, key],
root: tmpRuntimeData.root,
parent: destination,
key: key
});
} else {
destination[key] = deepmerge(target[key], source[key], optionsArgument, {
level: tmpRuntimeData.level + 1,
paths: [...tmpRuntimeData.paths, key],
root: tmpRuntimeData.root,
parent: destination,
key: key
});
}
});
return destination;
}
function _handleOptions(optionsArgument) {
const options = optionsArgument || {};
// @ts-ignore
return options;
}
function deepmerge(target, source, optionsArgument, tmpRuntimeData) {
const sourceIsArray = Array.isArray(source);
const targetIsArray = Array.isArray(target);
const options = _handleOptions(optionsArgument);
const sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
if (!sourceAndTargetTypesMatch) {
return cloneUnlessOtherwiseSpecified(source, optionsArgument, {
target,
source
}, tmpRuntimeData);
} else if (sourceIsArray) {
var _options$arrayMerge;
return ((_options$arrayMerge = options === null || options === void 0 ? void 0 : options.arrayMerge) !== null && _options$arrayMerge !== void 0 ? _options$arrayMerge : defaultArrayMerge)(target, source, optionsArgument, tmpRuntimeData);
} else {
return mergeObject(target, source, optionsArgument, tmpRuntimeData);
}
}
/**
* 檢查值是否為可合併物件 / Check if value is a mergeable object
*
* 這是一個便捷函式,包裝了 is-mergeable-object 模組
* 用於快速判斷給定的值是否可以被 deepmerge 合併
*
* This is a convenience function that wraps the is-mergeable-object module
* Used to quickly determine if a given value can be merged by deepmerge
*
* 可合併的類型通常包括:
* - 普通物件(plain objects)
* - 陣列(arrays)
* - 不可合併的類型(如原始類型、函式、Date 等)會回傳 false
*
* Mergeable types typically include:
* - Plain objects
* - Arrays
* - Non-mergeable types (such as primitives, functions, Date, etc.) return false
*
* @param value - 要檢查的值 / Value to check
* @returns 是否可合併(true 表示可以進行深度合併)/ Whether mergeable (true means deep merge can be performed)
*
* @example
* isMergeable({}) // 回傳 / returns true
* isMergeable([]) // 回傳 / returns true
* isMergeable('string') // 回傳 / returns false
* isMergeable(123) // 回傳 / returns false
* isMergeable(new Date()) // 回傳 / returns false
*/
function isMergeable(value) {
return isMergeableObject(value);
}
const SYMBOL_IS_MERGEABLE = /*#__PURE__*/Symbol.for('SYMBOL_IS_MERGEABLE');
/**
* 建立新的執行時資料 / Create new runtime data
*
* 在開始新的合併操作時呼叫此函式建立初始的 tmpRuntimeData
* 初始狀態為根層級,level 為 0,paths 為空陣列
*
* Called when starting a new merge operation to create initial tmpRuntimeData
* Initial state is root level, level is 0, paths is empty array
*
* @template R - root 物件的類型
* @template T - parent 物件的類型(預設與 R 相同)
* @param root - 初始的 destination 物件(將作為 root 傳遞)
* @returns 初始化的 tmpRuntimeData
*
* @example
* const data = _newTmpRuntimeData({});
* // data.level === 0
* // data.paths === []
* // data.root === {}
* // data.parent === {}
* // data.key === undefined
*/
function _newTmpRuntimeData(root) {
const tmpRuntimeData = {
level: 0,
paths: [],
root: root,
parent: root,
key: undefined
};
return tmpRuntimeData;
}
function deepmergeAll(array, optionsArgument) {
if (!Array.isArray(array)) {
throw new Error('first argument should be an array');
}
// @ts-ignore
return array.reduce(function (prev, next) {
return deepmerge(prev, next, optionsArgument);
}, {});
}
// @ts-ignore
{
Object.defineProperty(deepmerge, "__esModule", {
value: true
});
Object.defineProperty(deepmerge, 'deepmerge', {
value: deepmerge
});
Object.defineProperty(deepmerge, 'default', {
value: deepmerge
});
Object.defineProperty(deepmerge, 'isMergeable', {
value: isMergeable
});
Object.defineProperty(deepmerge, 'SYMBOL_IS_MERGEABLE', {
value: SYMBOL_IS_MERGEABLE
});
Object.defineProperty(deepmerge, 'deepmergeAll', {
value: deepmergeAll
});
Object.defineProperty(deepmerge, 'all', {
value: deepmergeAll
});
Object.defineProperty(deepmerge, '_isMergeableObject', {
value: _isMergeableObject
});
}
// @ts-ignore
module.exports = deepmerge;
//# sourceMappingURL=index.cjs.development.cjs.map