Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to supply custom cloing logic #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Install
Usage
-----

var deepExtend = require('deep-extend');
var deepExtend = require('deep-extend')();
var obj1 = {
a: 1,
b: 2,
Expand Down Expand Up @@ -56,3 +56,44 @@ Usage
e: { one: 1, two: 2 },
h: /abc/g }
*/

Custom Clone Logic
------------------

The deep-extend module will by default extend all types of objects. But
if you clone a custom object it will no longer be an instance of its
original class. If this is important to you, you can supply a custom cloning
function which you can use to implement your own cloning logic.

It will be called for every value in the object. If you return a falsy
value the default behavior of deep-extend will be used. If you return a
truthy value that value will be used as a replacement for the original
value.

var Foo = function(val) {
this.foo = val;
};
var Bar = function(val) {
this.bar = val;
};

var cloner = function (obj) {
// if given an instance of Foo, return a clone of it
if (obj instanceof Foo) return new Foo(obj.foo);
};

var deepExtend = require('deep-extend')(cloner);
var obj = {
a: new Foo(1),
b: new Bar(2)
};

var clone = deepExtend({}, obj);

console.log(clone);
/*
{ a: { foo: 1 },
b: { bar: 2 } }
*/
console.log(clone.a instanceof Foo); // true
console.log(clone.b instanceof Bar); // false
101 changes: 56 additions & 45 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,62 +32,73 @@
* If you wish to clone object, simply use that:
* deepExtend({}, yourObj_1, [yourObj_N]) - first arg is new empty object
*/
var deepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) {
if (arguments.length < 1 || typeof arguments[0] !== 'object') {
return false;
}

if (arguments.length < 2) return arguments[0];
var noop = function () {};

var target = arguments[0];
module.exports = function (cloner) {
if (!cloner) cloner = noop;

// convert arguments to array and cut off target object
var args = Array.prototype.slice.call(arguments, 1);
return function deepExtend(/*obj_1, [obj_2], [obj_N]*/) {
if (arguments.length < 1 || typeof arguments[0] !== 'object') {
return false;
}

var key, val, src, clone, tmpBuf;
if (arguments.length < 2) return arguments[0];

args.forEach(function (obj) {
if (typeof obj !== 'object') return;
var target = arguments[0];

for (key in obj) {
if ( ! (key in obj)) continue;
// convert arguments to array and cut off target object
var args = Array.prototype.slice.call(arguments, 1);

src = target[key];
val = obj[key];
var key, val, src, clone, tmpBuf, custom;

if (val === target) continue;
args.forEach(function (obj) {
if (typeof obj !== 'object') return;

if (typeof val !== 'object' || val === null) {
target[key] = val;
continue;
} else if (val instanceof Buffer) {
tmpBuf = new Buffer(val.length);
val.copy(tmpBuf);
target[key] = tmpBuf;
continue;
} else if (val instanceof Date) {
target[key] = new Date(val.getTime());
continue;
} else if (val instanceof RegExp) {
target[key] = new RegExp(val);
continue;
}
for (key in obj) {
if ( ! (key in obj)) continue;

if (typeof src !== 'object' || src === null) {
clone = (Array.isArray(val)) ? [] : {};
target[key] = deepExtend(clone, val);
continue;
}
src = target[key];
val = obj[key];

if (val === target) continue;

if (Array.isArray(val)) {
clone = (Array.isArray(src)) ? src : [];
} else {
clone = (!Array.isArray(src)) ? src : {};
if (custom = cloner(val)) {
target[key] = custom;
continue;
} else if (typeof val !== 'object' || val === null) {
target[key] = val;
continue;
} else if (val instanceof Buffer) {
tmpBuf = new Buffer(val.length);
val.copy(tmpBuf);
target[key] = tmpBuf;
continue;
} else if (val instanceof Date) {
target[key] = new Date(val.getTime());
continue;
} else if (val instanceof RegExp) {
target[key] = new RegExp(val);
continue;
}

if (typeof src !== 'object' || src === null) {
clone = (Array.isArray(val)) ? [] : {};
target[key] = deepExtend(clone, val);
continue;
}

if (Array.isArray(val)) {
clone = (Array.isArray(src)) ? src : [];
} else {
clone = (!Array.isArray(src)) ? src : {};
}

target[key] = deepExtend(clone, val);
}
});

target[key] = deepExtend(clone, val);
}
});
return target;
}
};

return target;
}
32 changes: 32 additions & 0 deletions test/cloner.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var should = require('should');
var Extend = require('../index');

var Foo = function(val) {
this.foo = val;
};
var Bar = function(val) {
this.bar = val;
};
var cloner = function(obj) {
if (obj instanceof Foo) return new Foo(obj.foo);
};

var extend = Extend(cloner);

describe('deep-extend with custom cloner', function() {

it('can extend on 1 level', function() {
var obj = {
a: new Foo(1),
b: new Bar(2)
};
var clone = extend({}, obj);
clone.should.eql({
a: { foo: 1 },
b: { bar: 2 }
});
clone.a.should.be.an.instanceof(Foo);
clone.b.should.not.be.an.instanceof(Bar);
});

});
2 changes: 1 addition & 1 deletion test/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var should = require('should');
var extend = require('../index');
var extend = require('../index')();

describe('deep-extend', function() {

Expand Down