@chaisser/merge-objects
v1.0.0
Published
Deep merge objects with array handling options
Maintainers
Readme
🔀 @chaisser/merge-objects
Deep merge objects with flexible array handling, depth control, and custom mergers
✨ Features
- 🎯 Type-safe - Full TypeScript support with generics
- 🔀 Deep merge - Recursively merge nested objects
- 📋 Array modes - Replace, concat, or merge-by-index
- 📏 Depth control - Limit recursion depth
- 🔧 Custom mergers - Per-key merge logic
- 🏗️ Merge all - Merge multiple objects in one call
- 🪶 Shallow merge - One-level merge when you don't need recursion
- 🔁 Merge defaults - Fill missing values without overwriting existing ones
- 🛡️ Immutable - Never mutates input objects
- 🪶 Zero dependencies - Lightweight and tree-shakeable
- 🏎️ ESM + CJS - Dual module format support
📦 Installation
npm install @chaisser/merge-objects
# or
yarn add @chaisser/merge-objects
# or
pnpm add @chaisser/merge-objects🚀 Quick Start
import { merge, mergeAll, mergeDefaults } from '@chaisser/merge-objects';
// Deep merge
merge(
{ user: { name: 'Alice', age: 30 } },
{ user: { age: 31, city: 'Istanbul' } }
);
// { user: { name: 'Alice', age: 31, city: 'Istanbul' } }
// Merge multiple objects
mergeAll([
{ a: 1 },
{ b: 2 },
{ a: 3, c: 4 },
]);
// { a: 3, b: 2, c: 4 }
// Fill defaults without overwriting
mergeDefaults(
{ name: 'Doruk' },
{ name: 'Anonymous', age: 25, active: true }
);
// { name: 'Doruk', age: 25, active: true }📖 What It Does
This package provides deep object merging utilities for JavaScript and TypeScript. It recursively combines nested objects, supports multiple array handling strategies (replace, concat, merge by index), allows depth limiting and custom per-key merge logic, and never mutates the input objects.
🎯 How It Works
The package provides 4 merge functions:
merge- Deep merge two objects with configurable optionsmergeAll- Deep merge an array of objects (left to right)shallowMerge- One-level merge using spread (no recursion)mergeDefaults- Fill missing keys from defaults without overwriting
Array handling modes:
replace(default) - Source array replaces target arrayconcat- Arrays are concatenated togethermerge- Arrays are merged element-by-element
🎨 What It's Useful For
- Config Merging - Combine default and user configurations
- State Management - Merge partial state updates
- API Responses - Combine data from multiple endpoints
- Form Data - Merge form state with defaults
- Theme Systems - Deep merge theme overrides
- Plugin Systems - Combine plugin configurations
💡 Usage Examples
Deep Merge
import { merge } from '@chaisser/merge-objects';
merge(
{ database: { host: 'localhost', port: 5432 } },
{ database: { port: 3306, user: 'admin' } }
);
// { database: { host: 'localhost', port: 3306, user: 'admin' } }Array Handling
// Replace (default)
merge({ tags: ['a', 'b'] }, { tags: ['c'] });
// { tags: ['c'] }
// Concat
merge({ tags: ['a', 'b'] }, { tags: ['c'] }, { arrayMode: 'concat' });
// { tags: ['a', 'b', 'c'] }
// Merge by index
merge(
{ users: [{ name: 'Alice' }, { name: 'Bob' }] },
{ users: [{ age: 30 }, { age: 25 }] },
{ arrayMode: 'merge' }
);
// { users: [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }] }Depth Control
merge(
{ a: { b: { c: 1 } } },
{ a: { b: { c: 2, d: 3 } } },
{ depth: 1 }
);
// { a: { b: { c: 2, d: 3 } } } — stops at depth 1, replaces { c: 1 } entirelyCustom Merger
merge(
{ count: 10, items: [1] },
{ count: 5, items: [2] },
{
customMerger: (key, left, right) => {
if (key === 'count') return (left as number) + (right as number);
// return undefined to use default merge behavior
},
}
);
// { count: 15, items: [2] }Merge Multiple Objects
import { mergeAll } from '@chaisser/merge-objects';
const defaults = { port: 8080, host: 'localhost', debug: false };
const envConfig = { port: 3000 };
const userConfig = { debug: true };
mergeAll([defaults, envConfig, userConfig]);
// { port: 3000, host: 'localhost', debug: true }
// With array concat
mergeAll(
[{ plugins: ['a'] }, { plugins: ['b'] }, { plugins: ['c'] }],
{ arrayMode: 'concat' }
);
// { plugins: ['a', 'b', 'c'] }Shallow Merge
import { shallowMerge } from '@chaisser/merge-objects';
shallowMerge(
{ a: { b: 1 }, x: 1 },
{ a: { c: 2 }, y: 2 }
);
// { a: { c: 2 }, x: 1, y: 2 } — 'a' is replaced, not deep-mergedMerge Defaults
import { mergeDefaults } from '@chaisser/merge-objects';
mergeDefaults(
{ name: 'Doruk' },
{ name: 'Anonymous', age: 25, active: true }
);
// { name: 'Doruk', age: 25, active: true }
// Existing values are preserved
mergeDefaults(
{ port: 3000 },
{ port: 8080, host: 'localhost' }
);
// { port: 3000, host: 'localhost' }📚 API Reference
merge(target, source, options?)
Deep merge two objects.
| Parameter | Type | Description |
|---|---|---|
| target | object | Base object |
| source | object | Object to merge in |
| options.arrayMode | 'replace' \| 'concat' \| 'merge' | How to handle arrays (default: 'replace') |
| options.depth | number | Max recursion depth (default: Infinity) |
| options.customMerger | (key, left, right) => unknown | Custom per-key merge logic |
Returns: New merged object (inputs are not mutated)
mergeAll(objects, options?)
Deep merge an array of objects, left to right.
| Parameter | Type | Description |
|---|---|---|
| objects | object[] | Objects to merge |
| options | MergeOptions | Same options as merge |
Returns: New merged object
shallowMerge(target, source)
Merge one level deep (equivalent to { ...target, ...source }).
Returns: New merged object
mergeDefaults(target, defaults)
Fill missing keys from defaults. Existing values in target are preserved.
| Parameter | Type | Description |
|---|---|---|
| target | object | Object with user-provided values |
| defaults | object | Default values to fill in |
Returns: New merged object
MergeOptions
interface MergeOptions {
arrayMode?: 'replace' | 'concat' | 'merge';
depth?: number;
customMerger?: (key: string, left: unknown, right: unknown) => unknown;
}🔗 Related Packages
Explore our other utility packages in the @chaisser namespace:
- @chaisser/merge-objects (this package) - Deep merge objects
- @chaisser/string-wizard - Advanced string manipulation
- @chaisser/type-guard - Runtime type guards and validators
- @chaisser/uuid-v7 - Time-ordered UUID v7 generator
- @chaisser/wait-for - Promise-based wait utilities
- @chaisser/regex-humanizer - Regex to human-readable descriptions
- @chaisser/password-strength - Password strength checker
- @chaisser/human-time - Human-readable time formatting
- @chaisser/obj-path - Safe dot-notation object access
- @chaisser/debounce-throttle - Rate limiting utilities
- @chaisser/color-utils - Color conversion utilities
- @chaisser/deep-clone - Deep cloning functions
- @chaisser/array-group-by - Array grouping utilities
- @chaisser/chunk-array - Array chunking functions
- @chaisser/event-emitter - Typed event emitter
🔒 License
MIT - Free to use in personal and commercial projects
👨 Developed by
Doruk Karaboncuk [email protected]
📄 Repository
- GitHub: @chaisser
- NPM: @chaisser/merge-objects
🤝 Contributing
Contributions are welcome! Feel free to:
- Report bugs
- Suggest new features
- Submit pull requests
- Improve documentation
📞 Support
For issues, questions, or suggestions, please reach out through:
- Email: [email protected]
- GitHub Issues: Create an issue
Made with ❤️ by @chaisser
