unclechu / node-deep-extend

Recursive extend module
MIT License
202 stars 53 forks source link

Add ability to supply custom cloing logic #13

Open watson opened 9 years ago

watson commented 9 years ago

Warning: This is a highly opinionated change so I understand if it doesn't fit the vision of this module. Also it should be released as a breaking change due to the new initializer function.

My use case is that I use deep-extend to clone MongoDB documents. They contain custom MongoDB types like Timestamp and ObjectId. This module will think they are regular object literals and extend them like any other object literal. This unfortunately means that they lose their relation to their original classes so you no longer can do an obj instanceof Klass.

This pull request adds new functionality which allows you to supply a custom cloning function which will be called for each value that deep-extend attempts to clone. If the cloning function returns a falsy value the regular behaviour of deep-extend is used, but if the cloning function returns any truthy value, that value will be used as the new 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
unclechu commented 9 years ago

@watson How about different interface? May be just add some methods to main function?

Something like this?:

var deepExtend = require('deep-extend');
var customExtend = deepExtend.customExtender(cloner);

var standardCloned = deepExtend({}, obj); // standard behavior
var customCloned = customExtend({}, obj); // with custom logic

It will save backward compatibility.

watson commented 9 years ago

That's not such a bad idea... you could also do it with a submodule like in is-my-json-valid:

var deepExtend = require('deep-extend');
var customExtend = require('deep-extend/custom')(cloner);

var standardCloned = deepExtend({}, obj); // standard behavior
var customCloned = customExtend({}, obj); // with custom logic

I kind of prefer the latter since adding a function to a custom property on the normal deepExtend function seems a little hack'ish. But I might be wrong.

I can update the pull request with which ever version you prefer?

unclechu commented 9 years ago

@watson Last version is better.

var deepExtend = require('deep-extend');
var customExtend = require('deep-extend/custom')(cloner);