diff --git a/index.js b/index.js index 06f1ad9..3238cfb 100755 --- a/index.js +++ b/index.js @@ -6,6 +6,8 @@ deap(deap, { clone: lib.clone, extend: lib.extend, update: lib.update, + merge: lib.merge, extendShallow: lib.extendShallow, - updateShallow: lib.updateShallow + updateShallow: lib.updateShallow, + mergeShallow: lib.mergeShallow }); diff --git a/lib/deap.js b/lib/deap.js index 6bd155e..f065334 100755 --- a/lib/deap.js +++ b/lib/deap.js @@ -6,7 +6,9 @@ module.exports = { extend: deepExtend, extendShallow: extend, update: deepUpdate, - updateShallow: update + updateShallow: update, + merge: deepMerge, + mergeShallow: merge }; function clone(val) { @@ -75,3 +77,29 @@ function deepUpdate(a, b /*, [b2..n] */) { }); return a; } + +function merge(a, b /*, [b2..n] */) { + slice.call(arguments, 1).forEach(function(b) { + Object.keys(b).forEach(function(p) { + if(!a.hasOwnProperty(p)) a[p] = b[p]; + }); + }); + return a; +} + +function deepMerge(a, b /*, [b2..n] */) { + slice.call(arguments, 1).forEach(function(b) { + var ap, bp, ta, tb; + Object.keys(b).forEach(function(p) { + ap = a[p]; + bp = b[p]; + ta = typeOf(ap); + tb = typeOf(bp); + if(tb === 'object' && ta === 'object') + deepMerge(ap, bp); + else if(!a.hasOwnProperty(p)) + a[p] = clone(bp); + }); + }); + return a; +} diff --git a/shallow.js b/shallow.js index 482aadb..4891175 100644 --- a/shallow.js +++ b/shallow.js @@ -5,5 +5,6 @@ var deap = module.exports = lib.extendShallow; deap(deap, { clone: lib.clone, extend: lib.extendShallow, - update: lib.updateShallow + update: lib.updateShallow, + merge: lib.mergeShallow }); diff --git a/test/deap.test.js b/test/deap.test.js index 64a358f..80d8457 100755 --- a/test/deap.test.js +++ b/test/deap.test.js @@ -34,5 +34,15 @@ describe('deap', function() { assert.deepEqual(deap.updateShallow, lib.updateShallow); }); + it('should have merge defined', function() { + assert.isFunction(deap.merge); + assert.deepEqual(deap.merge, lib.merge); + }); + + it('should have mergeShallow defined', function() { + assert.isFunction(deap.mergeShallow); + assert.deepEqual(deap.mergeShallow, lib.mergeShallow); + }); + }); diff --git a/test/merge.test.js b/test/merge.test.js new file mode 100644 index 0000000..42fa2fa --- /dev/null +++ b/test/merge.test.js @@ -0,0 +1,134 @@ +var lib = require('../lib/deap'), + assert = require('chai').assert; + +describe('shallow merge', function() { + var shallowMerge = lib.mergeShallow; + + it('should merge everything into an empty object', function() { + var a = { foo: 'bar' }, + result = shallowMerge({}, a); + + assert.deepEqual(result, a); + }); + + it('should return a reference to the first argument', function() { + var a = { burp: 'adurp' }, + b = { burp: 'zing', grr: 'arghh' }; + + var result = shallowMerge(a, b); + + assert.strictEqual(result, a); + }); + + it('should not replace existing values', function() { + var a = { burp: 'adurp' }, + b = { burp: 'zing', grr: 'arghh' }; + + var result = shallowMerge(a, b); + + assert.deepEqual(result, { burp: 'adurp', grr: 'arghh' }); + assert.equal(result.burp, a.burp); + }); + +}); + +describe('deep merge', function() { + var deepMerge = lib.merge; + + it('should return a reference to the first argument', function() { + var a = { burp: 'adurp' }, + b = { burp: 'zing', grr: 'arghh' }; + + var result = deepMerge(a, b); + + assert.strictEqual(result, a); + }); + + it('should merge a nested object one level deep', function() { + var a = { foo: 'bar', deep: { foo: 'bar', baz: 'buzz' }}, + b = { foo: 'bop', deep: { foo: 'beep', biz: 'baz' } }; + + var result = deepMerge(a, b); + + assert.equal(result.foo, 'bar'); + assert.equal(result.deep.foo, 'bar'); + assert.equal(result.deep.baz, 'buzz'); + assert.equal(result.deep.biz, 'baz'); + }); + + it('should merge a nested object two levels deep', function() { + var a = { foo: 'bar', deep: { hi: 'hello', deeper: { foo: 'bar', baz: 'buzz' }}}, + b = { foo: 'baz', deep: { hi: 'bye', bye: 'hi', deeper: { foo: 'beep', bop: 'boop' } } }; + + var result = deepMerge({}, a, b); + + assert.equal(result.foo, a.foo); + assert.isObject(result.deep); + assert.equal(result.deep.hi, a.deep.hi); + assert.equal(result.deep.bye, b.deep.bye); + assert.isObject(result.deep.deeper); + assert.equal(result.deep.deeper.foo, a.deep.deeper.foo); + assert.equal(result.deep.deeper.baz, a.deep.deeper.baz); + assert.equal(result.deep.deeper.bop, b.deep.deeper.bop); + }); + + it('should merge properties from multiple objects', function() { + var a = { foo: ['one'], boo: 'far', poo: 'tar' }, + b = { foo: ['two', 'three'], zoo: 'car' }, + c = { boo: 'star', two: 'czar' }; + + var result = deepMerge({}, a, b, c); + + assert.deepEqual(result, { + foo: a.foo, + boo: a.boo, + poo: a.poo, + zoo: b.zoo, + two: c.two + }); + }); + + it('should not preserve nested object references', function() { + var a = { foo: 'bar' }, + nested = { grr: 'argh' }, + newFoo = { burp: nested }, + b = { foo: newFoo, foo2: newFoo }; + + var result = deepMerge(a, b); + assert.equal(a.foo, 'bar'); + assert.deepEqual(a.foo2.burp, b.foo2.burp); + assert.notStrictEqual(a.foo2.burp, nested); + }); + + it('should not override a string with an object', function() { + var a = { foo: 'bar' }, + b = { foo: { biz: 'baz' } }; + + var result = deepMerge(a, b); + assert.deepEqual(a, { foo: 'bar' }); + }); + + it('should preserve array references', function() { + var a = { nested: [{ foo: 'bar' }] }, + b = { nested: [{ boo: 'far' }] }, + deep = a.nested; + + var result = deepMerge(a, b); + + assert.deepEqual(result.nested, a.nested); + assert.notStrictEqual(result.nested, b.nested); + assert.strictEqual(result.nested, deep); + }); + + it('should not preserve references in arrays', function() { + var a = { nested: [{ foo: 'bar' }] }, + b = { nested: [{ boo: 'far' }] }, + deeper = a.nested[0]; + + var result = deepMerge({}, a, b); + + assert.deepEqual(result.nested, a.nested); + assert.notStrictEqual(result.nested[0], deeper); + }); + +}); diff --git a/test/shallow.test.js b/test/shallow.test.js index e74a4d6..0d9319a 100644 --- a/test/shallow.test.js +++ b/test/shallow.test.js @@ -9,13 +9,15 @@ describe('shallow', function() { assert.isFunction(shallow.extend); assert.isFunction(shallow.update); + assert.isFunction(shallow.merge); assert.isFunction(shallow.clone); }); - it('should be shallow functions', function() { + it('should have shallow functions', function() { assert.equal(shallow, lib.extendShallow); assert.equal(shallow.extend, lib.extendShallow); assert.equal(shallow.update, lib.updateShallow); + assert.equal(shallow.merge, lib.mergeShallow); assert.equal(shallow.clone, lib.clone); });