A TypeScript library for performing deep merges of objects with advanced configuration options. Unlike Object.assign()
, this library creates new objects without mutating the original target, and provides fine-grained control over how different types of values are merged.
- Immutable Merging: Target objects are never mutated; new objects are always returned
- Deep Recursive Merging: Nested objects are merged recursively
- Type Safety: Full TypeScript support with generic types
- Class Instance Handling: Special handling for class instances vs plain objects
- Array Replacement: Arrays are treated as single values and replaced (not merged)
- Undefined Value Control: Optional merging of undefined values
- Fast Deep Cloning: Efficient deep cloning utility for simple objects
npm install @hyperse/deep-merge
# or
yarn add @hyperse/deep-merge
# or
pnpm add @hyperse/deep-merge
Performs a deep merge of two objects, where the source object's properties are merged into the target object.
target: T
- The target object to merge intosource: DeepPartial<T>
- The source object to merge frommergeUndefined?: boolean
- Whether to merge undefined values (default:false
)depth?: number
- The current depth of the merge (used internally, default:0
)
T
- A new object containing the merged properties
- Objects: Recursively merged if they are plain objects (not class instances)
- Arrays: Replaced entirely (not merged)
- Class Instances: Replaced entirely (not merged)
- Primitives: Replaced by source values
- Undefined Values: Only merged if
mergeUndefined
istrue
An extremely fast function for deep-cloning objects that contain only simple values (primitives, arrays, and nested simple objects).
input: T
- The input to clone
T
- The cloned input
- Primitives: Returned as-is
- Arrays: Deep cloned recursively
- Plain Objects: Deep cloned recursively
- Class Instances: Returned as-is (not cloned)
- Null/Undefined: Returned as-is
Checks if an item is a plain object (not an array or null).
Checks if an item is a class instance (has a constructor that's not Object).
A utility type that makes all properties of T
optional recursively.
import { mergeOptions } from '@hyperse/deep-merge';
const target = {
name: 'John',
age: 30,
address: {
city: 'New York',
country: 'USA',
},
hobbies: ['reading', 'swimming'],
};
const source = {
age: 31,
address: {
city: 'Los Angeles',
},
hobbies: ['coding'],
};
const result = mergeOptions(target, source);
// Result:
// {
// name: 'John',
// age: 31,
// address: {
// city: 'Los Angeles',
// country: 'USA'
// },
// hobbies: ['coding']
// }
import { mergeOptions } from '@hyperse/deep-merge';
const defaultConfig = {
dts: true,
input: { index: 'src/index.ts' },
logSilent: false,
modularImports: [],
plugin: {
extraPlugins: [],
pluginConfigs: {
multiInputOptions: { fastGlobOptions: { ignore: [] } },
replaceOptions: { 'process.env.NODE_ENV': '"production"' },
aliasOptions: { entries: [] },
nodeResolveOptions: {
extensions: ['.js', '.ts', '.tsx', '.json', '.vue'],
},
jsonOptions: {},
commonjsOptions: {},
babelOptions: { usePreset: 'node' },
terserOptions: true,
},
},
funcs: {
fn: () => {},
},
};
const userConfig = {
dts: {
entryPointOptions: {
libraries: { importedLibraries: ['@dimjs/utils'] },
},
},
input: 'src/index.ts',
plugin: {
extraPlugins: [],
pluginConfigs: {
terserOptions: {
came: 1,
},
},
},
output: { format: 'esm' },
};
const result = mergeOptions(defaultConfig, userConfig);
import { mergeOptions } from '@hyperse/deep-merge';
const target = {
a: 1,
b: { c: 2, d: 3 },
e: 'hello',
};
const source = {
a: undefined,
b: { c: undefined, f: 4 },
e: undefined,
};
// Without merging undefined values (default)
const result1 = mergeOptions(target, source);
// Result: { a: 1, b: { c: 2, d: 3, f: 4 }, e: 'hello' }
// With merging undefined values
const result2 = mergeOptions(target, source, true);
// Result: { a: undefined, b: { c: undefined, d: 3, f: 4 }, e: undefined }
import { simpleDeepClone } from '@hyperse/deep-merge';
const original = {
user: { name: 'John', preferences: { theme: 'dark' } },
settings: [1, 2, { nested: true }],
};
const clone = simpleDeepClone(original);
// Modifying the original doesn't affect the clone
original.user.name = 'Jane';
original.settings[0] = 999;
console.log(clone.user.name); // 'John'
console.log(clone.settings[0]); // 1
import { mergeOptions } from '@hyperse/deep-merge';
class User {
constructor(public name: string) {}
}
class Config {
constructor(public value: number) {}
}
const target = {
user: new User('John'),
config: new Config(100),
data: { x: 1, y: 2 },
};
const source = {
user: new User('Jane'),
data: { y: 3, z: 4 },
};
const result = mergeOptions(target, source);
// Class instances are replaced entirely
// result.user is the new User('Jane') instance
// result.data is { x: 1, y: 3, z: 4 }
Arrays are treated as single values and are completely replaced during merging:
const target = { items: [1, 2, 3] };
const source = { items: [4, 5] };
const result = mergeOptions(target, source);
// result.items is [4, 5], not [1, 2, 3, 4, 5]
Class instances are not recursively merged - they are replaced entirely:
class MyClass {
constructor(public value: number) {}
}
const target = { obj: new MyClass(1) };
const source = { obj: new MyClass(2) };
const result = mergeOptions(target, source);
// result.obj is the new MyClass(2) instance
The target object is never mutated. A new object is always returned:
const target = { a: 1, b: { c: 2 } };
const source = { b: { c: 3 } };
const result = mergeOptions(target, source);
console.log(target === result); // false
console.log(target.b === result.b); // false
The library provides full TypeScript support with generic types:
interface Config {
name: string;
settings: {
theme: string;
debug: boolean;
};
plugins: string[];
}
const defaultConfig: Config = {
name: 'default',
settings: { theme: 'light', debug: false },
plugins: [],
};
const userConfig: DeepPartial<Config> = {
settings: { theme: 'dark' },
};
const result = mergeOptions(defaultConfig, userConfig);
// result is typed as Config
mergeOptions
creates deep clones of the target object, so it's best suited for configuration merging rather than high-frequency operationssimpleDeepClone
is optimized for simple objects and is much faster than JSON.parse/stringify methods- Class instances are not cloned to preserve their behavior and methods
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.