Search code examples
javascriptobjectlodash

Deep object extending behavior with lodash


With the following

    var a = {
        a: 0,
        b: [],
        c: "",
        d: { "x": "y", "g": "f" },
        e: {},
        f: function () { return undefined; }(),
        g: false
    };
    var b = {
        a: { "a": "b" },
        b: { "c": "d" },
        c: { "e": "f" },
        d: { "g": "h" },
        e: { "i": "j" },
        f: {},
        g: { "m": "n" },
    };

If you look at the datatypes, I have the falsy or empty version of several (or a close approximation): Number, Array,String,Object,undefined, Boolean.

Using the above with the following:

    var assinged = _.assign({}, a, b);
    var merged = _.merge({}, a, b);
    var extended = _.extend({}, a, b);

I get:

assigned:

{
    "a": {"a": "b"},
    "b": {"c": "d"},
    "c": {"e": "f"},
    "d": {"g": "h"},
    "e": {"i": "j"},
    "g": {"m": "n"}
}

merged:

{
    "a":{"a":"b"},
    "b":[],
    "c":{"e":"f"},
    "d":{"x":"y","g":"h"},
    "e":{"i":"j"},
    "g":{"m":"n"}
}

extended:

{
    "a": {"a": "b"},
    "b": {"c": "d"},
    "c": {"e": "f"},
    "d": {"g": "h"},
    "e": {"i": "j"},
    "g": {"m": "n"}
}

SO, assign and extend are equivalent for this example. Merge does the closest to what I want - You look at the key d, you can see that my existing key is preserved, and the object is extended. However, looking at the b key, you can see that the empty array is overwriting the object with Merge.

Two questions:

  1. Is there a simple command in lodash that will achieve what I want (a true, complete deep merge, where all properties in the first object are overwritten or extended with values from the second object)?

  2. What choice is _.merge making here, that is causing this to happen?


Solution

  • However, looking at the b key, you can see that the empty array is overwriting the object with Merge

    Not overwriting, no.

    When you run _.merge({}, a, b).b you get an empty array with its c property set to "d". It's treating the array like an object, and effectively doing this:

    var b = [];
    b.c = "d";
    
    // inspect what b is now
    console.log(b.length); //=> 0
    console.log(b.c); //=> "d"
    

    In javascript an array is, effectively, an object that uses integers keys for many values:

    var a = [1,2,3];
    a.b = "c";
    console.log(Object.keys(a)); //=> ["0", "1", "2", "b"]
    

    You can create arbitrary properties on arrays and even functions in javascript, since they behave just like standard objects do.

    So, yeah, it's merging the properties of object b into object a recursively exactly as it should.


    You may want a more custom merging behavior that you write yourself. This question may help with that effort:

    How to deep merge instead of shallow merge?