Deeply
A toolkit for deep structure manipulations, provides deep merge/clone functionality out of the box,
and exposes hooks and custom adapters for more control and greater flexibility.
compression | size |
---|
deeply.js | 15.18 kB |
deeply.min.js | 5.06 kB |
deeply.min.js.gz | 1.5 kB |
Table of Contents
Install
$ npm install deeply --save
Examples
By default it provides interface for immutable operations,
also available via explicit require require('deeply/immutable')
or require('deeply').immutable
property.
Merging
Deeply merges two or more objects.
var merge = require('deeply');
var result = merge({a: {a1: 1}}, {a: {a2: 2}}, {b: {b3: 3}});
assert.equal(result, {a: {a1: 1, a2: 2}, b: {b3: 3}});
Cloning
As degenerated case of merging one object on itself, it's possible to use deeply as deep clone function.
var merge = require('deeply');
var clone = merge;
var x = {a: {b: {c: 1}}};
var y = clone(x);
y.a.b.c = 2;
assert.equal(x.a.b.c, 1);
Arrays Custom Merging
By default array treated as primitive values and being replaced upon conflict,
for more meaningful array merge strategy, provide one of the pre-built array merge helpers
or a custom reduce function within invocation context.
Default Behavior
var merge = require('deeply');
var result = merge({ a: { b: [0, 2, 4, {a: 'A'}], c: 'first' }}, { a: {b: [1, 3, 5, {b: 'B'}], d: 'second' }});
assert.equal(result, { a: { b: [1, 3, 5, {b: 'B'}], c: 'first', d: 'second' }});
Combining Arrays
var merge = require('deeply');
var result;
var context =
{
useCustomAdapters: merge.behaviors.useCustomAdapters,
'array' : merge.adapters.arraysCombine
};
result = merge.call(context, { a: { b: [0, {a: 'A1', b: 'B1'}, 4] }}, { a: {b: [1, {a: 'A2', c: 'C2'}, 5] }});
assert.equal(result, { a: { b: [1, {a: 'A2', b: 'B1', c: 'C2'}, 5] }});
Appending Arrays
var merge = require('deeply');
var context =
{
useCustomAdapters: merge.behaviors.useCustomAdapters,
'array' : merge.adapters.arraysAppend
};
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }});
assert.equal(result, { a: { b: [0, 2, 4, 4, 2, 0, 1, 3, 5, 5, 3, 1] }});
Appending Arrays and Keeping Unique Elements Only
var merge = require('deeply');
var context =
{
useCustomAdapters: merge.behaviors.useCustomAdapters,
'array' : merge.adapters.arraysAppendUnique
};
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }});
assert.equal(result, { a: { b: [0, 2, 4, 1, 3, 5] }});
Custom Merge Function
For example we need to have merging arrays to be appended,
with only unique elements and sort the result array.
var merge = require('deeply');
var context =
{
useCustomAdapters: merge.behaviors.useCustomAdapters,
'array' : appendUniqueAndSort
};
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }});
assert.equal(result, { a: { b: [0, 1, 2, 3, 4, 5] }});
function appendUniqueAndSort(to, from, merge)
{
from.forEach(function(v) { to.indexOf(v) == -1 && to.push(merge(undefined, v)); });
return to.sort();
}
Cloning Functions
By default, functions copied as is to the result object,
basically being treated as primitive values.
You can use functionsClone
adapter to clone functions,
creating new function object with the same signature as original.
var clone = require('deeply');
function subj(a, b)
{
return a + b + 10;
}
subj.customProp = 13;
subj.prototype.A = 1;
subj.prototype.B = {isB: 'true'};
var context =
{
useCustomAdapters: clone.behaviors.useCustomAdapters,
'function' : clone.adapters.functionsClone
};
var result = clone.call(context, { a: { b: subj}});
assert.equal(result, { a: { b: subj}});
assert.equal(subj.name, result.a.b.name);
assert.equal(subj.length, result.a.b.length);
assert.equal(subj.customProp, result.a.b.customProp);
subj.isOriginal = true;
result.a.b.isCopy = true;
assert.equal(subj.isOriginal, true);
assert.equal(subj.isCopy, undefined);
assert.equal(result.a.b.isOriginal, undefined);
assert.equal(result.a.b.isCopy, true);
assert.equal(subj(3, 4), result.a.b(3, 4));
Cloning Prototype Chain
It will also clone prototype objects,
so use this option with caution.
var clone = require('deeply');
function Subj()
{
this.boom = 'Zap!';
}
Subj.prototype.A = 1;
Subj.prototype.B = {isB: 'true'};
var context =
{
useCustomAdapters: clone.behaviors.useCustomAdapters,
'function' : clone.adapters.functionsClone
};
var result = clone.call(context, { class: Subj });
assert.equal(result, { class: Subj });
assert.equal(result.class.prototype.A, 1);
assert.equal(result.class.prototype.B.isB, 'true');
Subj.prototype.C = 2;
assert.equal(Subj.prototype.C, 2);
assert.equal(result.class.prototype.C, undefined);
var s1 = new Subj();
var s2 = new result.class();
assert.equal(s1.A, s2.A);
assert.equal(s1.B, s2.B);
assert.equal(s1.C, 2);
assert.equal(s2.C, undefined);
assert.equal(s1.boom, s2.boom);
assert.equal(s1 instanceof Subj, true);
assert.equal(s2 instanceof Subj, false);
Extend Original Function Prototype
When having proper instanceof
results matters,
you can use functionsExtend
helper instead.
var clone = require('deeply');
function Subj()
{
this.boom = 'Zap!';
return this.boom;
}
Subj.customProp = 13;
Subj.prototype.A = 1;
Subj.prototype.B = {isB: 'true'};
var context =
{
useCustomAdapters: clone.behaviors.useCustomAdapters,
'function' : clone.adapters.functionsExtend
};
var result = clone.call(context, { class: Subj });
assert.equal(result, { class: Subj });
assert.equal(Subj.name, result.class.name);
assert.equal(Subj.length, result.class.length);
assert.equal(Subj.customProp, result.class.customProp);
Subj.isOriginal = true;
result.class.isCopy = true;
assert.equal(Subj.isOriginal, true);
assert.equal(Subj.isCopy, undefined);
assert.equal(result.class.isOriginal, undefined);
assert.equal(result.class.isCopy, true);
assert.equal(Subj(), result.class());
assert.equal(result.class.prototype.A, 1);
assert.equal(result.class.prototype.B.isB, 'true');
Subj.prototype.X = 67;
assert.equal(Subj.prototype.X, 67);
assert.equal(result.class.prototype.X, 67);
var s1 = new Subj();
var s2 = new result.class();
assert.equal(s1.A, s2.A);
assert.equal(s1.B, s2.B);
assert.equal(s1.boom, s2.boom);
assert.equal(s1 instanceof Subj, true);
assert.equal(s2 instanceof Subj, true);
Custom hooks
As shown in Custom Merge Function example,
you can add custom adapters for any data type
that supported by precise-typeof.
For this example we will combine arrays of number,
by performing addition operation on array elements.
var merge = require('deeply');
var context =
{
useCustomAdapters: merge.behaviors.useCustomAdapters,
'array' : merge.adapters.arraysCombine,
'number' : addNumbers
};
var result = merge.call(context, { a: { b: [0, 2, 4, 4, 2, 0] }}, { a: {b: [1, 3, 5, 5, 3, 1] }}, { a: {b: [7, 8, 9, 10, 11, 12] }});
assert.equal(result, { a: { b: [8, 13, 18, 19, 16, 13] }});
function addNumbers(to, from)
{
return (to || 0) + from;
}
Mutable Operations
Mutable interface supports all the described operations,
and available via explicit require require('deeply/mutable')
or require('deeply').mutable
property.
var merge = require('deeply/mutable');
var myObj = {a: {a1: 1, a2: 2}, b: {b1: 11, b2: 12}};
merge(myObj, {c: 'c', d: 'd'}, {x: {y: {z: -Infinity}}});
assert.equal(myObj, {a: {a1: 1, a2: 2}, b: {b1: 11, b2: 12}, c: 'c', d: 'd', x: {y: {z: -Infinity}}});
Ludicrous Mode
Also as shortcut and a homage to Tesla, ludicrous mode is available,
that will clone functions and it's prototype objects by default. :)
Details could be found in Cloning Functions examples.
var ludicrous = require('deeply/ludicrous');
var scopeVar = 6;
function original(a, b)
{
return a + b + scopeVar;
}
var cloned = ludicrous({ func: original });
assert.equal(cloned, { func: original });
assert.equal(original.name, cloned.func.name);
assert.equal(original.length, cloned.func.length);
original.isOriginal = true;
cloned.func.isCopy = true;
assert.equal(original.isOriginal, true);
assert.equal(original.isCopy, undefined);
assert.equal(cloned.func.isOriginal, undefined);
assert.equal(cloned.func.isCopy, true);
assert.equal(original(1, 2), cloned.func(1, 2));
Note: ludicrous
isn't included into the main deeply
package, so it won't be automatically pulled in,
if you're bundling using browserify deeply/index.js
, to use ludicrous in the browser you'd need to explicitly
require it in your modules or specify direct path in your bundler config.
Want to Know More?
More examples can be found in test/compatability.js.
Or open an issue with questions and/or suggestions.
License
Deeply is licensed under the MIT license.