merge-dictionaries

1.0.0 • Public • Published

merge-dictionaries

A wrapper around the Lodash 3 .merge() function that addresses some issues with arrays and object references. Intended for merging configuration files together.

Usage

npm install merge-dictionaries
var mergeDictionaries = require('merge-dictionaries');
mergeDictionaries(dictA, dictB);

What’s the problem that this solves?

The .merge() function works great in most cases, but the default behavior has two problems:

First, array values in the second argument are merged weirdly with values in the first argument. Examples:

// Two arrays are "merged" together by replacing values in the first array with values from
// the second, by index:
var dictA = { foo: ['owl', 'snake', 'fish'] };
var dictB = { foo: ['cat', 'dog']};
_.merge(dictA, dictB);

// Results in:
// { foo: ['cat', 'dog', 'fish'] }


// Merging an array into a string causes the string to be busted up into an array of characters,
// which is then merged on-top-of as above:
var dictA = { foo: 'abcde' };
var dictB = { foo: ['cat', 'dog']};
_.merge(dictA, dictB);

// Results in:
// { foo: ['cat', 'dog', 'c', 'd', 'e'] }

Second, dictionaries in the second argument that do not have corresponding dictionary values in the first argument (or whose corresponding values are {}) are copied over by value instead of by reference:

var dictA = { foo: 'bar' };
var dictB = { nested: { owl: 'hoot' } };
var owl = dictB.nested; // <-- owl is { owl: 'hoot' }
var merged = _.merge(dictA, dictB);

// Results in:
// { foo: 'bar', nested: { owl: 'hoot' } }

console.log(owl === merged.nested);
// Results in:
// false

This might not seem like a big issue, but it can be a real problem when merging dictionaries that contain references to objects created by another module. For example imagine:

var configA = { someConfigValue: 'some default value' };
var configB = { someConfigValue: 'a custom value',  someModule: require('my-module') };
var mergedObj = _.merge(configA, configB);

where my-module looks like:

module.exports = ( function() {
  // Declare the public data dictionary exposed by this module.
  var publicData = {};
  return {
    // Expose the public data to the outside world.
    somePublicData: publicData
    // Declare a function for initializing the module.
    init: function() {
      publicData.foo = 'bar';
    }
  }
} )()

If you call mergedObj.someModule.init() later, you might expect mergedObj.someModule.somePublicData to be set to {foo: 'bar'}, but it’ll still just be an empty dictionary, because a different somePublicData dictionary was copied into the merged object.

What’s the solution?

The solution is very simple, because the _.merge() function can take a third argument that allows you to customize the merge behavior. We can use this to tell _.merge() to only do its regular thing when the left-hand value is a non-empty plain dictionary. In all other cases, a is replaced by b.

Keep in mind that this means that if a looks like a dictionary, but was created by a custom constructor (i.e. it is not a “plain” dictionary, it will be replaced by b! For example:

var myClass = function() {this.foo = 'bar'};
var obj1 = { abc: new myClass() };
// Result:
// { abc: { foo: 'bar' } }

var obj2 = { abc: { owl: 'hoot' } };
var merged = mergeDictionaries(obj1, obj2);

// Result:
// { abc: { owl: 'hoot' } }

Help

If you have questions or are having trouble, click here.

Bugs   NPM version

To report a bug, click here.

Contributing

Please observe the guidelines and conventions laid out in the Sails project contribution guide when opening issues or submitting pull requests.

NPM

License

Like the Sails framework, this package is free and open-source under the MIT License.

Readme

Keywords

none

Package Sidebar

Install

npm i merge-dictionaries

Weekly Downloads

33,941

Version

1.0.0

License

MIT

Last publish

Collaborators

  • sgress454