-1

I want to merge two arrays of objects. Those objects have the same structure, but one of them is missing the hide property. I want to copy the value of hide property from one object to the other that is missing this property. The important part is that I don't want to mutate any of these arrays!

The first array looks like this (notice that there is hide property):

let first_array = [
    {
        name: 'John',
        age: 40,
        hide: true,
        childs: [
            {
                name: 'Alice',
                age: 20,
                hide: false,
                childs: [
                    {
                        name: 'Mike',
                        age: 2,
                        hide: true
                    }
                ]
            }
        ]
    },
    {
        name: 'Peter',
        age: 40,
        hide: true,
        childs: [
            {
                name: 'Andrew',
                age: 20,
                hide: true,
                childs: [
                    {
                        name: 'Jessica',
                        age: 2,
                        hide: true
                    }
                ]
            }
        ]
    }
]

The second array looks almost the same! The only thing missing is hide property.

let second_array = [
    {
        name: 'John',
        age: 40,
        childs: [
            {
                name: 'Alice',
                age: 20,
                childs: [
                    {
                        name: 'Mike',
                        age: 2,
                    }
                ]
            }
        ]
    },
    {
        name: 'Peter',
        age: 40,
        childs: [
            {
                name: 'Andrew',
                age: 20,
                childs: [
                    {
                        name: 'Jessica',
                        age: 2,
                    }
                ]
            }
        ]
    }
]

Now, I want to create new array with where within each object there is hide property.

I know how to do this recursively in the imperative way, but unfortunately I'm mutating data - which I don't want to do.

function getHideProperty(first, second) {
    for (let i = 0; i < second.length; i++) {
        for (let j = 0; j < first.length; j++) {
            if (second[i].name === first[j].name) {
                second[i].hide = first[j].hide
                if (second[i].childs) {
                    second[i].childs = getHideProperty(first[j].childs, second[i].childs)
                }
            }
        }
    }
    return second
}

Now I can create new array with merged objects:

const newArray = getHideProperty(second_array, first_array)

Now, every object in second_array has hide property. But I mutated the array :(

How to achieve such result without mutating the array?

sympi
  • 735
  • 3
  • 11
  • 17
  • 1
    Technically, of course, you're *not* mutating the array. You're mutating objects that the array refers to. Which matters in terms of thinking of the solution. – T.J. Crowder Jul 10 '17 at 17:44
  • By _mutating_ I mean that at the beginning the value of some array was X, after passing it to the function, the value of this array is no longer X. The array was mutated – sympi Jul 10 '17 at 17:50
  • 1
    Well, again, it isn't (the values in the array -- *references* to objects -- are unchanged in your example), but it's only important in terms of helping see what's required to avoid it: Creating new objects (and a new array). :-) – T.J. Crowder Jul 10 '17 at 17:51

2 Answers2

2

You'll need to:

  1. Create a new array to store the new information, and return that

  2. Deep-copy second[i] to store in the new array, prior to modifying anything

For #2, choose your favorite answer from What is the most efficient way to deep clone an object in JavaScript?

For #1, very roughly (see comments):

function getHideProperty(first, second) {
    const result = []; // Our result array
    for (let i = 0; i < second.length; i++) {
        const secondEntry = result[i] = deepCopy(second[i]); // The deep copy, and let's avoid constantly re-retrieving second[i]/result[i]
        for (let j = 0; j < first.length; j++) {
            if (secondentry.name === first[j].name) {
                secondentry.hide = first[j].hide
                if (secondEntry.childs) {
                    // Could be more efficient here, since the entries in `childs` are already copies; left as an exercise to the reader...
                    secondEntry.childs = getHideProperty(first[j].childs, secondEntry.childs)
                }
            }
        }
    }
    return result;
}

This is not meant to be an all-singing, all-dancing solution. It's meant to help you along the way. Note the deepCopy placeholder for your preferred solution to #2. :-)


If you do something like the above (nested loops) and find that it's a performance problem, you can create a Map of the entries in first keyed by their names, and then look them up in the map when looping through second (rather than the nested loop). The complexity is only useful if you run into a performance problem with the simple nested loops solution.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Wow. Thank you so much for help and tips. I used Object.assign for deepCopy and it works like a charm. And yeah, I was mutating the objects in the array, not array itself. I will consider using Map here – sympi Jul 10 '17 at 18:08
  • 1
    @sympi: Beware that `Object.assign` is not a deep copy, so any subordinate objects (such as the `childs` array) will not be copied, they'll be shared. Mikael's answer avoids that problem by providing a new `childs` property for the new object, which is fine for your stated data; but unless you do that explicitly for all subordinate objects, you'll get ugly cross-talk. – T.J. Crowder Jul 10 '17 at 18:17
  • Hmm, so this is actually super weird. In my real data, there is like 9 levels of deepness and I'm using object spread (which eventually gets transpilled to Object.assign?) for copying and it looks like working fine. – sympi Jul 10 '17 at 18:25
  • 1
    @sympi: It's fine if there are multiple levels, you're replacing `childs` anyway. The issue would be if you had another subordinate object on these, which you weren't replacing. Maybe you don't now; but if you add one later, you may not think to go back and fix this, and then you'd end up with ugly cross-references between the old and new data. – T.J. Crowder Jul 10 '17 at 18:29
  • Okay, thank you again for help! I'm going to use cloneDeep from lodash – sympi Jul 10 '17 at 18:44
2

This is a functional approach that doesn't mutate any of the original arrays or their items:

function getHideProperty(first, second) {
    return second.map(function(item) {
        var corresponding = first.find(function(searchItem) {
            return searchItem.name === item.name;
        });
        return Object.assign({},
          item,
          { hide: corresponding.hide },
          item.childs
            ? { childs: getHideProperty(item.childs, corresponding.childs) } 
            : {}
        );
    });
}
Lennholm
  • 7,205
  • 1
  • 21
  • 30
  • 2
    It is, however, fragile (slight changes to the structure result in the new array having references to parts of the old objects), and involves creating a lot of extra temporary objects. – T.J. Crowder Jul 10 '17 at 18:15