43

I am struggling with deep copies of objects in nodeJS. my own extend is crap. underscore's extend is flat. there are rather simple extend variants here on stackexchange, but none are even close to jQuery.extend(true, {}, obj, obj, obj) .. (most are actually terrible and screw up the benefits of asnyc code.)

hence, my question: is there a good deep copy for NodeJS? Has anybody ported jQuery's ?

itsatony
  • 987
  • 1
  • 9
  • 13
  • Avoid doing so. Deep copies are bad. Favour shallow copies. – Raynos Feb 22 '12 at 18:02
  • 3
    could you explain why? for me, the shallow copies are a nightmare when they flow into a series of async callbacks... – itsatony Feb 22 '12 at 19:12
  • 1
    also - our DB structure (mongoDB) has pretty deep objects and i really do not want to mess around and convert structures... it's very convenient to just work with the very same objects in code & db ... – itsatony Feb 22 '12 at 19:16
  • Sure it is. Just don't deep copy them. I work with objects from mongo and I never deep copy them :\ – Raynos Feb 22 '12 at 19:31
  • 4
    itsatony I disagree with Raynos here, you should use your judgement as to whether this behaviour is right for your use case. Just be aware that there are pitfalls and use your head. This is a debate on the deep copy/extend issue for the Underscore project: https://github.com/documentcloud/underscore/issues/162 – Richard Marr Feb 27 '12 at 11:08
  • You can use this plugin https://github.com/maxmara/dextend – Vlad Miller Feb 03 '13 at 16:14

11 Answers11

28

It's already been ported. node-extend

Note the project doesn't have tests and doesn't have much popularity, so use at your own risk.

As mentioned you probably don't need deep copies. Try to change your data structures so you only need shallow copies.

Few months later

I wrote a smaller module instead, recommend you use xtend. It's not got an implementation containing jQuery baggage nor does it have bugs like node-extend does.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 4
    Sorry, how can you say that just because *you* have never used a deep copy that they're bad and should be avoided in all cases? – Josh Feb 22 '12 at 19:44
  • 1
    @Josh I didn't say they are bad, I just said avoid them. They are computationally expensive and have a bunch of annoying edge cases you have to be aware of. Deep copies are unnecessary complexity that is easy to avoid – Raynos Feb 22 '12 at 19:49
  • Node-extend 1.0.0 doesn't work for deep copies. I've fixed my local copy and sent the changes to the author: a) rename call on line 16 from **hasOwn** to **hasOwnProperty** b) give the main function a name on line 19: **function extend ()** `{ var options, name, ...` so the recursive call will work and then just set `module.exports = extend;` after that function, on the last line of the source file (line 83). – Brent Faust Mar 23 '12 at 03:30
  • thanks for xtend ! we're testing it today. in general we had trouble with all extends (including jquery and sugarJS versions) when we tried to deep-extend arrays. behaviour in that case was always strange and definitely unexpected. we will post a fiddle/gist on the problem here asap. – itsatony Aug 02 '12 at 08:45
  • 3
    @itsatony xtend only does shallow extension by design – Raynos Aug 02 '12 at 23:09
  • 5
    after trying out a few modules I choose for [node.extend](https://github.com/dreamerslab/node.extend) because it clones objects using prototypes correctly. xtend and node-extend (with a '-') both fail to do so. – donnut Sep 09 '12 at 14:07
  • 3
    @Raynos you should tell you are the author of the library you promote. – benweet Feb 07 '14 at 22:30
  • @benweet does it matter, it's a library that does one thing well. – Raynos Feb 08 '14 at 00:05
  • 2
    Even if you are 100% impartial you have to "disclose your affiliation" as stated [here](http://meta.stackoverflow.com/help/behavior) – benweet Feb 08 '14 at 01:09
  • 1
    You do need to disclose your involvement in a project you link to here. Please fix this or your answer might be deleted if it's flagged more. – Bill the Lizard Apr 30 '14 at 12:27
  • 1
    @benweet I added disclosure as requested. – Raynos May 07 '14 at 22:42
  • Could you please explain how you support your claim of, "nor does it have bugs"? Which bugs are you referring to? – jacobq Nov 10 '16 at 18:04
15

You want jQuery's, so just use it:

function extend() {
    var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false,
        toString = Object.prototype.toString,
        hasOwn = Object.prototype.hasOwnProperty,
        push = Array.prototype.push,
        slice = Array.prototype.slice,
        trim = String.prototype.trim,
        indexOf = Array.prototype.indexOf,
        class2type = {
          "[object Boolean]": "boolean",
          "[object Number]": "number",
          "[object String]": "string",
          "[object Function]": "function",
          "[object Array]": "array",
          "[object Date]": "date",
          "[object RegExp]": "regexp",
          "[object Object]": "object"
        },
        jQuery = {
          isFunction: function (obj) {
            return jQuery.type(obj) === "function"
          },
          isArray: Array.isArray ||
          function (obj) {
            return jQuery.type(obj) === "array"
          },
          isWindow: function (obj) {
            return obj != null && obj == obj.window
          },
          isNumeric: function (obj) {
            return !isNaN(parseFloat(obj)) && isFinite(obj)
          },
          type: function (obj) {
            return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"
          },
          isPlainObject: function (obj) {
            if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) {
              return false
            }
            try {
              if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
                return false
              }
            } catch (e) {
              return false
            }
            var key;
            for (key in obj) {}
            return key === undefined || hasOwn.call(obj, key)
          }
        };
      if (typeof target === "boolean") {
        deep = target;
        target = arguments[1] || {};
        i = 2;
      }
      if (typeof target !== "object" && !jQuery.isFunction(target)) {
        target = {}
      }
      if (length === i) {
        target = this;
        --i;
      }
      for (i; i < length; i++) {
        if ((options = arguments[i]) != null) {
          for (name in options) {
            src = target[name];
            copy = options[name];
            if (target === copy) {
              continue
            }
            if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
              if (copyIsArray) {
                copyIsArray = false;
                clone = src && jQuery.isArray(src) ? src : []
              } else {
                clone = src && jQuery.isPlainObject(src) ? src : {};
              }
              // WARNING: RECURSION
              target[name] = extend(deep, clone, copy);
            } else if (copy !== undefined) {
              target[name] = copy;
            }
          }
        }
      }
      return target;
    }

and a small test to show that it does deep copies

extend(true, 
    {
        "name": "value"
    }, 
    {
        "object": "value",
        "other": "thing",
        "inception": {
            "deeper": "deeper",
            "inception": {
                "deeper": "deeper",
                "inception": {
                    "deeper": "deeper"
                }
            }
        }
    }
)

But remember to provide attribution: https://github.com/jquery/jquery/blob/master/src/core.js

jcolebrand
  • 15,889
  • 12
  • 75
  • 121
  • Please note that I did not bring over "isPlainObject", "isArray" or any other jQuery files, because I wanted to point out that you can directly capture their source and just use that. – jcolebrand Feb 22 '12 at 17:30
  • cool, thanks a ton! I had tried getting it over myself, but i must have messed it up. yours works, mine didn't :( – itsatony Feb 22 '12 at 19:14
  • Works like charm! Couldn't use jQuery in Google Appscript, and this helped me a lot!! – Jay Dadhania Nov 30 '19 at 09:55
11

A quick and dirty answer to deep copies is just to cheat with a little JSON. It's not the most performant, but it does do the job extremely well.

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}
Kato
  • 40,352
  • 6
  • 119
  • 149
  • 3
    That's great if it's just a data oriented object but you wouldn't want to do that if your object came from a particular constructor with it's own methods and inheritance as that would all be lost. – marksyzm Feb 25 '13 at 10:44
  • 2
    @marksyzm that's absolutely true; it's only useful for copying simple objects of values; it fails for dates, functions, and in some instances constructed objects. – Kato Feb 25 '13 at 14:40
  • unfortunately functions are lost. This works perfecty for everything but functions – gabrielstuff Dec 30 '15 at 21:40
  • 2
    No, it doesn't work perfectly for everything but functions. To quote the comment directly before yours: `it's only useful for copying simple objects of values; it fails for dates, functions, and in some instances constructed objects.` – Kato Dec 31 '15 at 02:32
  • Cloning is not necessarily extending. Extending requires a target. – Michael Franzl Apr 25 '17 at 16:11
11

Please use the built-in util module:

var extend = require('util')._extend;

var merged = extend(obj1, obj2);
ScriptMaster
  • 127
  • 1
  • 2
8

I know this is an old question, but I'd just like to throw lodash's merge into the mix as a good solution. I'd recommend lodash for utility functions in general :)

thataustin
  • 2,035
  • 19
  • 18
  • I like lodash, but lodash's `extend` mutates the object and this sucks big time. – Wtower Dec 12 '16 at 00:02
  • Lodash's mutation can easily be avoided with an empty object as the first parameter for both `merge` and `extend`. `var obj3 = lodash.extend(obj1, obj2)` will mutate `obj1` `var obj3 = lodash.extend({}, obj1, obj2)` will not mutate `obj1`. – edgar.bjorntvedt Feb 08 '17 at 20:37
1

This works for deep object extension... be warned that it replaces arrays rather than their values but that can obviously be updated how you like. It should maintain enumeration capabilities and all the other stuff you probably want it to do

function extend(dest, from) {
    var props = Object.getOwnPropertyNames(from), destination;

    props.forEach(function (name) {
        if (typeof from[name] === 'object') {
            if (typeof dest[name] !== 'object') {
                dest[name] = {}
            }
            extend(dest[name],from[name]);
        } else {
            destination = Object.getOwnPropertyDescriptor(from, name);
            Object.defineProperty(dest, name, destination);
        }
    });
}
marksyzm
  • 5,281
  • 2
  • 29
  • 27
1

In Node.js, You can use Extendify to create an _.extend function that supports nested objects extension (deep extend) and is also immutable to it's params (hence deep clone).

_.extend = extendify({
    inPlace: false,
    isDeep: true
});
1

node.extend does it deep and has familiar jQuery syntax

Stanislav
  • 4,389
  • 2
  • 33
  • 35
1

just install extend. docs: node extend package install:

npm install extend

then enjoy it:

extend ( [deep], target, object1, [objectN] )

deep is optional. default is false. if switch to true it will recursively merge your objects.

Amin Jalali
  • 320
  • 1
  • 5
  • 15
0

Sharped version called whet.extend.

I re-write node-extend with CoffeeScript and add travis-ci test suite, because I need deep coping in Node for myself, so now it is here.

And yes, I think in some case its absolutely correctly to use deep merge, for example I use it at config works, when we are need to merge default and user branches together.

Meettya
  • 345
  • 2
  • 9
0

You also can use my version of extend plugin https://github.com/maxmara/dextend

Vlad Miller
  • 2,255
  • 1
  • 20
  • 38