Skip to content

Latest commit

 

History

History
144 lines (108 loc) · 5.27 KB

File metadata and controls

144 lines (108 loc) · 5.27 KB

merge

The merge function is a powerful, flexible tool for merging objects, arrays, and their nested properties. It supports deep merging, merging multiple sources, property descriptor cloning, array merging strategies, and custom filtering logic.

Features

  • Deep Merging: Option to deeply merge objects and arrays.
  • Multiple Sources: Merge an array of objects into a target object.
  • Merge Arrays: Choose between overwriting, appending, or merging unique array elements.
  • Property Descriptors: Clone getter/setter and other property descriptors.
  • Custom Filters: Exclude specific properties based on custom logic.
  • Security: Built-in protection against prototype pollution (__proto__, constructor).
  • TypeScript Ready: Full type safety and intelligent merging of source types.

Basic Usage

import { merge } from '@jsopen/objects';

const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };

merge(target, source);
// target is now: { a: 1, b: 3, c: 4 }

Merging Multiple Sources

const target = { a: 1 };
const sources = [{ b: 2 }, { c: 3 }];

merge(target, sources);
// target is now: { a: 1, b: 2, c: 3 }

Options

The merge function accepts an optional Options object as its third argument.

Option Type Default Description
deep boolean | 'full' | CallbackFn false Enables deep merging. true only for plain objects/arrays, 'full' for all objects.
mergeArrays boolean | 'unique' | CallbackFn false Controls how arrays are merged.
keepExisting boolean | CallbackFn false If true, does not overwrite existing properties in the target.
copyDescriptors boolean false If true, copies property descriptors (getters, setters, etc.).
symbolKeys boolean true Whether to include properties with Symbol keys.
ignoreUndefined boolean true If true, undefined values in source will be ignored.
ignoreNulls boolean false If true, null values in source will be ignored.
ignoreSource CallbackFn - Callback to ignore specific source values.
filter CallbackFn - Callback to exclude properties from both target and source.

Deep Merging

  • true: Merges plain objects and arrays recursively. Non-plain objects (class instances) are assigned by reference.
  • 'full': Merges all objects including class instances (excluding built-in types like Date, RegExp, etc.).
  • CallbackFn: A function (value, context) => boolean to decide whether to go deep for a specific path.
merge(target, source, { deep: true });

// Using a callback to deep merge only specific paths
merge(target, source, {
  deep: (val, { path }) => path.startsWith('metadata')
});

Array Merging

  • true: Appends source array elements to the target array.
  • 'unique': Appends source array elements and ensures the resulting array has unique values.
  • CallbackFn: A function (value, context) => boolean to decide whether to merge arrays for a specific path.
const target = { tags: ['js'] };
const source = { tags: ['ts', 'js'] };

merge(target, source, { mergeArrays: 'unique' });
// target.tags is now: ['js', 'ts']

Property Descriptors

By default, merge copies values. Enable copyDescriptors to preserve getters, setters, and other attributes.

const source = {
  get id() {
    return Math.random();
  }
};

merge(target, source, { copyDescriptors: true });
// target now has the 'id' getter, not just a static value.

Filtering and Ignoring

merge(target, source, {
  // Ignore source values that are booleans
  ignoreSource: (val) => typeof val === 'boolean',

  // Ignore specific keys
  filter: (val, { key }) => key !== 'internalSecret',

  // Keep target's value if it already exists
  keepExisting: true
});

Security

The function automatically skips keys that could lead to prototype pollution:

  • __proto__
  • constructor

Type Definitions

type CallbackFn = (value: any, ctx: CallbackContext) => boolean;

interface CallbackContext {
  source: any;
  target: any;
  key: string | symbol | number;
  path: string; // Dot-separated path, e.g., "user.profile.id"
}

interface Options {
  deep?: boolean | 'full' | CallbackFn;
  symbolKeys?: boolean;
  mergeArrays?: boolean | 'unique' | CallbackFn;
  keepExisting?: boolean | CallbackFn;
  copyDescriptors?: boolean;
  ignoreSource?: CallbackFn;
  ignoreUndefined?: boolean;
  ignoreNulls?: boolean;
  filter?: CallbackFn;
}