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-mergePerforms 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 
mergeUndefinedistrue 
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]); // 1import { 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) instanceThe 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); // falseThe 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 ConfigmergeOptionscreates deep clones of the target object, so it's best suited for configuration merging rather than high-frequency operationssimpleDeepCloneis 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.