objectenvy / merge
Function: merge()
ts
function merge<T, U>(
obj1,
obj2,
options?): Merge<T, U>;Defined in: objectEnvy.ts:890
Recursively merge two configuration objects, with obj2 winning on conflicts.
Type Parameters
| Type Parameter |
|---|
T extends ConfigObject |
U extends ConfigObject |
Parameters
| Parameter | Type | Description |
|---|---|---|
obj1 | T | The base configuration object. |
obj2 | U | The second configuration object; its keys take precedence over obj1. |
options | MergeOptions | Merge options, including arrayMergeStrategy. |
Returns
Merge<T, U>
A new object containing all keys from both inputs, with obj2 values winning conflicts.
Remarks
merge performs a symmetric deep merge: for each key present in obj2, its value overwrites the corresponding key in obj1. Nested objects are merged recursively. Arrays are handled according to options.arrayMergeStrategy:
'replace'(default):obj2's array replacesobj1's array.'concat': arrays fromobj1andobj2are joined (obj1first, thenobj2).'concat-unique': same as concat but duplicate primitive values are removed; object items are deduplicated by deep JSON equality.
The return type is Merge<T, U> (from type-fest), which correctly models obj2 keys shadowing obj1 keys at the type level.
Use When
- You need to combine two configuration objects where neither is the authoritative "defaults" — e.g., merging a base config with a feature-flag overlay.
- You're composing multiple partial config slices loaded from different sources.
- You need array concatenation across config layers (
concatorconcat-unique).
Avoid When
- You want one object to be authoritative "defaults" and the other to win — use
override()instead. - You need to merge more than two objects — chain
merge(merge(a, b), c)calls.
Pitfalls
- NEVER rely on
merge()to deep-clone the inputs — BECAUSE nested sub-objects are shallow-copied at each level, so mutations to deeply nested objects in the result affect the originals. - NEVER use
'concat-unique'to deduplicate object items if equality matters beyond JSON serialisation — BECAUSE the implementation usesJSON.stringifyfor comparison, which is order-sensitive and ignoresundefinedvalues,Dateobjects, and prototype methods. - NEVER assume
merge()handles non-plain objects (Map, Set, Date, class instances) correctly — BECAUSE the function checkstypeof === 'object'and recurses, producing incorrect results for these types.
Examples
ts
// Deep merge with obj2 winning on shared keys
import { merge } from 'objectenvy';
const config1 = { port: 3000, log: { level: 'info' } };
const config2 = { log: { path: '/var/log' }, debug: true };
const merged = merge(config1, config2);
// { port: 3000, log: { level: 'info', path: '/var/log' }, debug: true }ts
// Concatenate arrays from two sources
import { merge } from 'objectenvy';
const config1 = { tags: ['prod', 'v1'] };
const config2 = { tags: ['api'] };
const merged = merge(config1, config2, { arrayMergeStrategy: 'concat' });
// { tags: ['prod', 'v1', 'api'] }ts
// Deduplicate while merging host lists
import { merge } from 'objectenvy';
const config1 = { hosts: ['localhost', 'example.com'] };
const config2 = { hosts: ['example.com', 'api.example.com'] };
const merged = merge(config1, config2, { arrayMergeStrategy: 'concat-unique' });
// { hosts: ['localhost', 'example.com', 'api.example.com'] }See
override for defaults-style merging where the second argument wins on missing keys only
Default Value
options.arrayMergeStrategy defaults to 'replace'