deepmerge-plus
Version:
用於深度(遞迴)合併 JavaScript 物件的函式庫 / A library for deep (recursive) merging of JavaScript objects
427 lines (423 loc) • 18.4 kB
TypeScript
import { ITSPickExtra, ITSPropertyKey, ITSWriteable } from 'ts-type';
/**
* 決定是否啟用深度複製功能
* 預設為 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.
*/
export declare function _shouldClone(optionsRuntime?: IOptions): boolean;
/**
* 檢查目標物件中該 key 的值是否為 undefined
* Check if target key value is undefined
*/
export declare function _defaultCheckShouldNotUpsertValue(value: any, optionsRuntime: IOptions, tmpRuntimeTarget: ICache, tmpRuntimeData: ITmpRuntimeData): boolean;
export declare function _isUndefined(value: unknown): value is undefined;
export declare function _isNull(value: unknown): value is null;
export declare function _isNullOrUndefined(value: unknown): value is null;
/**
* 檢查值是否為可合併物件 / 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
*/
export declare function _isMergeableObject(value: any, optionsArgument: IOptions, tmpRuntimeTarget: ICache, tmpRuntimeData: ITmpRuntimeData): boolean;
/**
* 深度合併結果類型 / Deep merge result type
*
* 合併兩個物件後的類型,推斷規則如下:
* 1. 取得 T2 中不在 T1 的鍵(新增的鍵)
* 2. 與 T1 的鍵進行交集(保留 T1 的鍵)
* 3. 確保結果為可寫入類型
*
* Type after merging two objects, inference rules:
* 1. Get keys in T2 that are not in T1 (new keys)
* 2. Intersect with keys in T1 (preserve T1's keys)
* 3. Ensure result is writeable type
*
* @template T1 - 目標物件類型 / Target object type
* @template T2 - 來源物件類型 / Source object type
*/
export type IDeepmergeResult<T1, T2> = ITSWriteable<ITSPickExtra<T2, Exclude<keyof T2, keyof T1>> & T1>;
/**
* 初始化選項
* 若未提供則使用預設設定(使用預設陣列合併函式)
*
* Initialize options
* Use default settings if not provided (use default array merge function)
*/
export declare function _handleOptions(optionsArgument?: IOptions): IOptions;
/**
* 深度合併函式 / Deep merge function
*
* 主要的合併函式,處理物件和陣列的深度合併
* Main merge function that handles deep merging of objects and arrays
*
* 處理邏輯 / Processing logic:
* 1. 檢查來源和目標是否為陣列
* 2. 檢查類型是否匹配(兩者都必須是陣列或都不是)
* 3. 若類型不匹配,返回來源物件的克隆
* 4. 若兩者都是陣列,使用 arrayMerge 函式合併
* 5. 否則,使用 mergeObject 進行物件合併
*
* @param T1 - 目標物件類型 / Target object type
* @param T2 - 來源物件類型 / Source object type
* @param target - 目標物件 / Target object
* @param source - 來源物件 / Source object
* @param optionsArgument - 合併選項 / Merge options
* @returns 合併後的物件 / Merged object
*/
export declare function deepmerge<T1 extends unknown[], T2 extends unknown[]>(x: T1, y: T2, options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): [
...T1,
...T2
];
export declare function deepmerge<T1, T2>(x: T1[], y: T2[], options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): (T2 | T1)[];
export declare function deepmerge<T1, T2>(x: T1, y: T2, options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): IDeepmergeResult<T1, T2>;
export declare function deepmerge<T>(x: Partial<NoInfer<T>>, y: Partial<NoInfer<T>>, options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): ITSWriteable<T>;
/**
* 內部快取介面 / Internal cache interface
*
* 用於在合併過程中傳遞上下文資訊
* 這些資訊在遞迴合併時用於維持物件間的引用關係
*
* 與 ITmpRuntimeData 的差異:
* - ICache: 追蹤 source、target、destination 的引用關係
* - ITmpRuntimeData: 追蹤遞迴路徑和深度資訊
*
* Used to pass context information during merge operations
* This information is used during recursive merge to maintain object references
*
* Difference from ITmpRuntimeData:
* - ICache: Tracks source, target, destination reference relationships
* - ITmpRuntimeData: Tracks recursion path and depth information
*
* @example
* {
* key: 'user', // 目前處理的鍵名
* source: { ... }, // 來源物件
* target: { ... }, // 目標物件
* destination: { ... } // 目的物件(目前合併結果)
* }
*
* `tmpRuntimeTarget: ICache`
*/
export interface ICache {
/** 鍵名 / Key name */
key?: any;
/** 來源物件 / Source object */
source?: any;
/** 目標物件 / Target object */
target?: any;
/** 目的物件 / Destination object */
destination?: any;
}
export type IAnyRecord = Record<ITSPropertyKey | number, any>;
/**
* 執行時資料介面 / Runtime data interface
*
* 用於在 deepmerge 遞迴呼叫過程中傳遞上下文資訊
* 追蹤目前在合併樹中的位置和路徑
*
* Used to pass context information during deepmerge recursive calls
* Tracks current position and path in the merge tree
*
* 屬性說明 / Properties Description:
* - level: 目前的遞迴深度(根為 0,每遞迴一次 +1)
* - paths: 從根到目前位置的路徑陣列(可用於 _.get(root, paths))
* - root: 最頂層的 destination 物件(永遠不變)
* - parent: 目前層級的父物件
* - key: 目前正在處理的鍵名
*
* 遞迴流程範例 / Recursive Flow Example:
* 合併 { a: { b: 1 } } 和 { a: { c: 2 } } 時:
* - 根層級: level=0, paths=[], root={}, parent={}, key=undefined
* - level1: level=1, paths=['a'], root={}, parent={}, key='a'
* - level2: level=2, paths=['a','b'], root={}, parent={a:{}}, key='b'
*
* @template T - parent 物件的類型(通常與 root 相同或為其子物件)
* @template R - root 物件的類型
*/
export interface ITmpRuntimeData<T extends IAnyRecord = IAnyRecord, R extends IAnyRecord = IAnyRecord> {
/** 遞迴深度計數(根為 0)/ Recursion depth count (root is 0) */
readonly level: number;
/** 從根到目前位置的路徑陣列 / Path array from root to current position */
readonly paths: (keyof R | keyof T | ITSPropertyKey | number)[];
/** 最頂層的 destination 物件(永遠不變)/ Top-level destination object (always unchanged) */
readonly root: R;
/** 目前層級的父物件 / Parent object at current level */
readonly parent: T;
/** 目前正在處理的鍵名 / Key currently being processed */
readonly key: keyof T;
}
/**
* 合併選項介面 / Merge options interface
*
* 定義 deepmerge 函式的可選配置參數
* Allows customization of merge behavior
*
* Defines optional configuration parameters for deepmerge function
*/
export interface IOptions {
/**
* 決定是否啟用深度複製功能
* 預設為 true(啟用),除非選項明確設為 false
*
* Determine whether to enable deep cloning
* Default is true (enabled), unless options explicitly set to false
*
* @note 注意事項 / Note:
* - 即使設為 `false`,在部分狀況下仍可能會忽略此設定並產生 clone
* - 此行為為正常現象,因為深度合併需要確保輸出物件與輸入物件相互獨立
*
* - Even if 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
*
* @default true
*/
clone?: boolean;
/**
* 自訂陣列合併函式
* Custom array merge function
*
* 允許自訂陣列的合併行為
* 預設行為是串接兩個陣列
*
* Allows customization of array merge behavior
* Default behavior is to concatenate two arrays
*
* @param destination - 目標陣列 / Destination array
* @param source - 來源陣列 / Source array
* @param options - 合併選項 / Merge options
* @returns 合併後的陣列 / Merged array
*/
arrayMerge?<T extends any[]>(target: T, source: any[], options?: IOptions, tmpRuntimeData?: ITmpRuntimeData<T>): T[];
arrayMerge?(target: any[], source: any[], options?: IOptions, tmpRuntimeData?: ITmpRuntimeData): any[];
/**
* 自訂可合併物件判斷函式
* Custom mergeable object check function
*
* 允許自訂哪些物件可以被合併
* 可用於支援自訂類型(如 Map、Set 等)
*
* Allows customizing which objects can be merged
* Can be used to support custom types (like Map, Set, etc.)
*
* @param value - 要檢查的值 / Value to check
* @param isMergeableObject - 預設的判斷函式 / Default check function
* @param optionsArgument - 合併選項 / Merge options
* @param tmpRuntimeData - 執行時資料 / Runtime data
*/
isMergeableObject?(value: any, isMergeableObject: typeof isMergeable, optionsArgument?: IOptions, tmpRuntimeTarget?: ICache, tmpRuntimeData?: ITmpRuntimeData): void;
isMergeableObject?(value: any, isMergeableObject: typeof isMergeable, optionsArgument?: IOptions, tmpRuntimeTarget?: ICache, tmpRuntimeData?: ITmpRuntimeData): boolean;
/**
* (val = old || new) 模式
* (val = old || new) mode
*
* 啟用時會保留目標中已存在的值
* 當來源物件和目標物件都有相同鍵時,優先使用目標的值
*
* When enabled, preserves values that already exist in target
* When both source and target have the same key, priority is given to target's value
*
* @deprecated 棄用,建議使用 `keyValueUpsertMode` 取代
* @deprecated Deprecated, use `keyValueUpsertMode` instead
*
* @note 注意事項 / Note:
* 此選項保留為相容性用途,不建議在新程式碼中使用。
* 若有需要自訂合併邏輯,建議使用 `keyValueUpsertMode` 選項。
*
* This option is kept for backward compatibility and is not recommended for new code.
* For custom merge logic, use the `keyValueUpsertMode` option instead.
*
* @example
* const target = { name: 'Alice', age: 25 };
* const source = { name: 'Bob', city: 'Taipei' };
* // keyValueOrMode: true 時 / when keyValueOrMode: true
* // 結果: { name: 'Alice', age: 25, city: 'Taipei' }
* // name 保留目標的值,age 來自目標,city 來自來源
*/
keyValueOrMode?: boolean;
/**
* Upsert 模式(選擇性更新)
* Upsert mode (selective update)
*
* 控制何時應該更新目標中的值
* 為 true 時:保留原有值(不取代),只在目標 key 的值為 undefined 時才使用來源的值
* 為 function 時:只在函式回傳 true 時保留原有值(不取代)
*
* Control when to update values in target
* When true: Preserve existing values (don't replace), only use source's value when target key value is undefined
* When function: Only preserve existing values (don't replace) when function returns true
*
* @note 重要說明 / Important Note:
* 當 keyValueUpsertMode 為 true 時,表示「不取代」模式:
* - 如果目標值不是 undefined,則保留目標的原始值
* - 只有當目標值是 undefined 時,才會使用來源的值
* 這包括 null、0、false、空字串等 falsy 值,都會被保留
*
* When keyValueUpsertMode is true, it means "don't replace" mode:
* - If target value is not undefined, preserve the original target value
* - Only use source's value when target value is undefined
* This includes falsy values like null, 0, false, empty string, which are all preserved
*
* @note 升級說明 / Upgrade Note:
* 此選項為 `keyValueOrMode` 的升級版本,解決了以下問題:
* - 使用 `??` 取代 `||` 運算子,避免 falsy 值(如 `0`、`''`、`false`)被錯誤處理
* - 支援自訂函式,可根據條件靈活決定是否保留目標的值
* - 行為更直觀,true 時明確表示「保留原有值」
*
* This option is an upgraded version of `keyValueOrMode`, solving the following issues:
* - Uses `??` instead of `||` operator to avoid incorrect handling of falsy values (like `0`, `''`, `false`)
* - Supports custom functions for flexible conditional logic
* - More intuitive behavior, true clearly means "preserve existing values"
*
* @example
* const target = { name: 'Alice', age: undefined, count: 0, email: null };
* const source = { name: 'Bob', age: 30, count: 5, email: 'bob@example.com' };
* // keyValueUpsertMode: true 時 / when keyValueUpsertMode: true
* // 結果: { name: 'Alice', age: 30, count: 0, email: null }
* // name: 保留目標值 'Alice'(不是 undefined)
* // age: 使用來源值 30(目標值是 undefined)
* // count: 保留目標值 0(不是 undefined)
* // email: 保留目標值 null(不是 undefined)
*
* @example
* // 使用自訂函式 / Using custom function
* keyValueUpsertMode: (value, options, tmpRuntimeTarget, tmpRuntimeData) => {
* return tmpRuntimeTarget.target?.[tmpRuntimeTarget.key] !== undefined;
* }
*/
keyValueUpsertMode?: boolean | ((value: unknown, optionsRuntime?: IOptions, tmpRuntimeTarget?: ICache, tmpRuntimeData?: ITmpRuntimeData) => boolean);
}
/**
* 檢查值是否為可合併物件 / 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
*/
export declare function isMergeable(value: any): boolean;
/**
* 可合併物件標記符號 / Mergeable object marker symbol
*
* 全域符號,用於在物件上標記是否為可合併物件
* 這是一個全域可共享的 Symbol,可跨模組使用
*
* Global symbol used to mark whether an object is mergeable
* This is a globally shareable Symbol that can be used across modules
*
* 用法 / Usage:
* ```typescript
* const obj = { name: 'test' };
* obj[SYMBOL_IS_MERGEABLE] = true; // 標記為可合併 / Mark as mergeable
* obj[SYMBOL_IS_MERGEABLE] = false; // 標記為不可合併 / Mark as not mergeable
* ```
*
* 優勢 / Advantages:
* - 可以自訂物件的可合併性
* - 可以自訂物件的可合併性
* - 避免與物件原有屬性衝突
* - Avoid conflicts with object's original properties
*/
export declare const SYMBOL_IS_MERGEABLE: unique symbol;
/**
* 建立新的執行時資料 / 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
*/
export declare function _newTmpRuntimeData<R extends ITmpRuntimeData["root"], T extends IAnyRecord = R>(root: R): ITmpRuntimeData<T, R>;
/**
* 合併多個物件 / Merge multiple objects
*
* 將陣列中的所有物件依序合併成單一物件
* 這是 deepmerge 的陣列版本,適用於合併多個物件
*
* Merges all objects in the array into a single object sequentially
* This is the array version of deepmerge, suitable for merging multiple objects
*
* 處理邏輯 / Processing logic:
* 1. 驗證輸入參數是否為陣列
* 2. 使用 reduce 依序合併所有物件
* 3. 初始值為空物件,每次迭代都會與下一個物件合併
*
* @param array - 要合併的物件陣列 / Array of objects to merge
* @param optionsArgument - 合併選項 / Merge options
* @returns 合併後的物件 / Merged object
* @throws 若第一個參數不是陣列,則拋出錯誤 / Throws error if first argument is not an array
*
* @example
* deepmergeAll([{ a: 1 }, { b: 2 }, { c: 3 }])
* // 回傳 / returns { a: 1, b: 2, c: 3 }
*/
export declare function deepmergeAll<T1, T2 = any>(array: [
T1,
...T2[]
], optionsArgument?: IOptions): IDeepmergeResult<T1, T2>;
export declare function deepmergeAll<T1>(array: T1[], optionsArgument?: IOptions): T1;
export {
deepmerge as default,
deepmergeAll as all,
};
export {};