IonIon
Packages
  • next
  • current
Changelog
GitHub
Packages
  • next
  • current
Changelog
GitHub
  • Version 0.x

    • Release Notes
    • Upgrade Guide
    • Contribution Guide
    • Security Policy
    • Code of Conduct
    • Origin
  • Packages

    • Introduction
    • Container

      • Introduction
      • Prerequisites
      • How to install
      • Container Instance
      • Bindings
      • Dependencies
      • Resolving
      • Contextual Bindings
    • Contracts

      • Introduction
      • How to install
    • Support

      • Introduction
      • How to install
      • Arrays

        • About Arrays
        • Includes All
        • Includes Any
        • Is Array Like
        • Is Concat Spreadable
        • Is Safe Array Like
        • Is Typed Array
        • Merge
      • Concerns

        • About Concerns
        • Prerequisites
        • Concern Class
        • Using Concerns
        • Aliases
        • Conflict Resolution
        • Booting
        • Hooks
        • Edge Cases
        • JSDoc
      • Exceptions

        • About Exceptions
        • Configure Custom Error
        • Configure Stack Trace
        • Get Error Message
        • Custom Errors
      • Facades

        • About Facades
      • Meta

        • About Meta
        • Prerequisites
        • Supported Elements
        • Set & Get
        • Inheritance
        • Outside Changes
        • TC39 Proposal
        • Target Meta
      • Mixins

        • About Mixins
        • New Mixin
        • Apply Mixins
        • Instanceof
        • Inheritance
        • Onward
      • Object

        • About Objects
        • Forget
        • Forget All
        • Get
        • Has
        • Has All
        • Has Any
        • Has Unique ID
        • Is Cloneable
        • Is Populatable
        • Isset
        • Merge
        • Populate
        • Set
        • Unique ID
      • Reflections

        • About reflections
        • Assert Has Prototype Prop.
        • Class Looks Like
        • Class Own Keys
        • Get All Parents Of Class
        • Get Class Prop. Descriptor
        • Get Class Prop. Descriptors
        • Get Constructor Name
        • Get Name Or Desc. Tag
        • Get Parent Of Class
        • Has All Methods
        • Has Method
        • Has Prototype Property
        • Is Callable
        • Is Class Constructor
        • Is Class Method Reference
        • Is Constructor
        • Is Key Safe
        • Is Key Unsafe
        • Is Method
        • Is Subclass
        • Is Subclass Or Looks Like
        • Is WeakKind
      • Misc

        • About Misc.
        • Desc. Tag
        • Empty
        • Is Key
        • Is Primitive
        • Is Property Key
        • Isset
        • Merge Keys
        • To Weak Ref.
      • Callback Wrapper
    • Vuepress Utils

      • Introduction
      • How to install
      • Navigation

        • Archive
      • Plugins

        • Last Updated
      • Components

        • Version Disclaimer
    • XYZ (test package)

merge Available since v0.9

Merges objects recursively into a new object. The properties and values of the source objects are copied, using deep copy techniques, when possible. Behind the scene, most value types are deep copied via structuredClone.

  • Shallow Copied Types
  • Unsafe Keys
  • Merge Options
    • depth
    • skip
    • overwriteWithUndefined
    • useCloneable
    • mergeArrays
    • arrayMergeOptions Available since v0.11
    • callback

Example

import { merge } from "@aedart/support/objects";

const person = {
    'name': 'Alice',
};

const address = {
    'address': {
        'street': 'Somewhere Street 43'
    },
};

const result = merge(person, address);

console.log(result);

The above shown example results in a new object that looks like this:

{
    "name": "Alice",
    "address": {
        "street": "Somewhere Street 43"
    }
}

Shallow Copied Types

Be default, the following value types are only shallow copied:

  • function
  • Symbol
const a = {
    'foo': null,
    'bar': Symbol('my_symbol')
};

const b = {
    'foo': function() {},
};

const result = merge(a, b);

console.log(result.foo === b.foo); // true
console.log(result.bar === a.bar); // true

Unsafe Keys

Property keys that are considered "unsafe", are never copied.

const a = {
    'foo': 'bar'
};
const b = {
    __proto__: { 'is_admin': true }
}

const result = merge(a, b);

console.log(result); // { 'foo': 'bar' }
console.log(Reflect.has(result, '__proto__')); // false

See isUnsafeKey() for additional details.

Merge Options

merge() supports a number of options. To specify thom, use the using() method.

merge()
    .using({ /** option: value */ })
    .of(objA, objB, objC);

Note

When invoking merge() without any arguments, an underlying objects Merger instance is returned.

depth

The depth option specifies the maximum merge depth.

  • Default maximum depth: 512

A MergeError is thrown, if the maximum depth is exceeded.

const a = {
    'person': {
        'name': 'Una'
    }
};

const b = {
    'person': {         // Level 0
        'age': 24,      // Level 1
        'address': {
            'street': 'Somewhere Str. 654' // Level 2
        }
    }
};

const result = merge()
    .using({
        depth: 1
    })
    .of(a, b); // MergeError - Maximum merge depth (1) has been exceeded

skip

skip defines property keys that must not be merged.

It accepts an array of property keys or a callback.

List of property keys

const a = {
    'person': {
        'name': 'Ulrik'
    }
};

const b = {
    'person': {
        'age': 36,
        'address': {
            'street': 'Nowhere Str. 12'
        }
    }
};

const result = merge()
    .using({
        skip: [ 'age' ]
    })
    .of(a, b);

The above example results in the following new object:

{
    "person": {
        "name": "Ulrik",
        "address": {
            "street": "Nowhere Str. 12"
        }
    }
}

Note

When specifying a list of property keys, then the depth level in which the property key is found does not matter.

Skip Callback

You can use a callback, if you need to handle more advanced skip logic. The callback accepts the the following arguments:

  • key: PropertyKey - The current property that is being processed.
  • source: object - The source object that contains the key.
  • result: object - The resulting object (relative to the current depth that is being processed).

The callback MUST return a boolean value; true if given key must be skipped, false otherwise.

const a = {
    'person': {
        'name': 'Jane'
    }
};

const b = {
    'person': {
        'name': 'James',
        'address': {
            'street': 'Sunview Palace 88'
        }
    }
};

const b = {
    'person': {
        'name': 'White',
    }
};

const result = merge()
    .using({
        skip: (key, source, result) => {
            return key === 'name'
                && source[key] !== null
                && !Reflect.has(result, key); 
        }
    })
    .of(a, b);

The above example results in the following new object:

{
    "person": {
        "name": "Jane",
        "address": {
            "street": "Sunview Palace 88"
        }
    }
}

overwriteWithUndefined

Determines if a property value should be overwritten with undefined.

Note: By default, all values are overwritten, even when they are undefined!

const a = { 'foo': true };
const b = { 'foo': undefined };

merge(a, b); // { 'foo': undefined }

merge()
    .using({ overwriteWithUndefined: false })
    .of(a, b) // { 'foo': true }

useCloneable

Determines if an object's return value from a clone() method (see Cloneable) should be used for merging, rather than the source object itself.

Note: By default, if an object is cloneable, then its return value from clone() is used.

const a = { 'foo': { 'name': 'John Doe' } };
const b = { 'foo': {
     'name': 'Jane Doe',
     clone() {
         return {
             'name': 'Rick Doe',
             'age': 26
         }
     }
} };

merge(a, b); // { 'foo': { 'name': 'Rick Doe', 'age': 26 } }

merge()
    .using({ useCloneable: false })
    .of(a, b); // { 'foo': { 'name': 'Jane Doe', clone() {...} } }

mergeArrays

When enabled, arrays, array-like, and concat spreadable objects are merged.

Note: By default, existing array values are NOT merged.

const a = { 'foo': [ 1, 2, 3 ] };
const b = { 'foo': [ 4, 5, 6 ] };

merge(a, b); // { 'foo': [ 4, 5, 6 ] }

merge()
    .using({ mergeArrays: true })
    .of(a, b); // { 'foo': [ 1, 2, 3, 4, 5, 6 ] }

Behind the scene, the array merge utility is used for merging arrays.

arrayMergeOptions Available since v0.11

See Array Merge Options.

callback

In situations when you need more advanced merge logic, you may specify a custom callback.

The callback is responsible for returning the value to be merged, from a given source object.

const a = {
    'a': 1
};

const b = {
    'b': 2
};

const result = merge()
    .using({
        callback: (target, next, options) => {
            const { key, value } = target;
            if (key === 'b') {
                return value + 1;
            }

            return value;
        }
    })
    .of(a, b); // { 'a': 1, 'b': 3 }

If you do not have other merge options to specify, then you can simply provide a merge callback directly as argument for the using() method.

const result = merge()
    .using((target, next, options) => {
        const { key, value } = target;
        if (key === 'b') {
            return value + 1;
        }

        return value;
    })
    .of(a, b);

Arguments

The merge callback is given the following arguments:

  • target: MergeSourceInfo - The source target information (see below).
  • next: NextCallback - Callback to invoke for merging nested objects (next depth level).
  • options: Readonly<MergeOptions> - The merge options to be applied.

target: MergeSourceInfo

The source target information object contains the following properties:

  • result: object - The resulting object (relative to object depth)
  • key: PropertyKey - The target property key in source object to.
  • value: any - Value of the property in source object.
  • source: object - The source object that holds the property key and value.
  • sourceIndex: number - Source object's index (relative to object depth).
  • depth: number - The current recursion depth.

next: NextCallback

The callback to perform the merging of nested objects. It accepts the following arguments:

  • sources: object[] - The nested objects to be merged.
  • options: Readonly<MergeOptions> - The merge options to be applied.
  • nextDepth: number - The next recursion depth number.

Onward

For additional information about the merge callback, please review the source code of the defaultMergeCallback(), inside @aedart/support/objects.

Edit page
Last Updated:
Contributors: alin
Prev
Isset
Next
Populate