Home > Blockchain >  Javascript update items in array without overriding the array, update only the object items
Javascript update items in array without overriding the array, update only the object items

Time:10-26

I have a problem with updating items in a array.

I receive from mongodb via change streams, updated collection items. These object needs to be merged with a existing object that include arrays.

The following examples illustrates what the core problem is (Minimalized):

const data = {
    name: "Name1",
    desc: "Description",
    values: [{
        key: "key1",
        value: null,
        desc: "desc 1"
    }, {
        key: "key2",
        value: true,
        desc: "desc 2"
    }]
};

// log original data
console.log(data);


// update object coems from mongodb via change stream
const update = {
    desc: "New Description",
    values: [{
        key: "key1",
        value: true
    }, {
        key: "key2",
        value: false
    }]
};

// update mechanism
Object.assign(data, update);

// display updated data
console.log(data);

The problem with Object.assign is, the .values array from the original data is override and not merged.

I need a function that merges every object, no matter if the object is inside a array or not. This must work recursive, and no matter how nested.

I came up with a function that looks like this: merge.js

function merge(target, source) {

    let loop = (dst, src) => {
        if (src instanceof Object) {

            Object.keys(src).forEach((key) => {
                if (src[key] instanceof Array) {

                    dst[key] = src[key].map((item, i) => {
                        return Object.assign(dst[key][i], item);
                    });

                } else if (src[key] instanceof Object) {

                    loop(dst[key], src[key]);

                } else {

                    dst[key] = src[key];

                }
            });

        } else {

            throw new TypeError(`src is not a instance of object`)

        }
    };

    loop(target, source);

}

Minimal reproducible example:

const util = require("util");
const _merge = require("./merge.js");

const data = {
name: "Name1",
desc: "Description",
config: {
    foo: "bar"
},
values: [{
    key: "key1",
    value: null,
    desc: "desc 1",
    services: [{
        name: "http",
        port: 80,
        connections: []
    }]
}, {
    key: "key2",
    value: true,
    desc: "desc 2",
    services: [
        {
            name: "http",
            port: 80,
            connections: []
        }, {
            name: "ws",
            port: 8080,
            connections: []
        }
    ]
}]
};

// log original data
console.log(data);


// update object coems from mongodb via change stream
const update = {
desc: "New Description",
config: {
    foo: "baz"
},
values: [{
    key: "key1",
    value: true,
    desc: "new Descipriotn",
}, {
    key: "key2",
    value: false,
    services: [{
        name: "https",
        port: 443
    }, {
        name: "ws",
        port: 8433
    }]
}]
};

// update mechanism
//Object.assign(data, update);
_merge(data, update);

// display updated data
console.log(util.inspect(data, false, 10, true));

The problem with the merge function is, that when a property exists inside the original object inside a arrays like connection, this does not exists in the update target.

Output:

{
  name: 'Name1',
  desc: 'New Description',
  config: { foo: 'baz' },
  values: [
    {
      key: 'key1',
      value: true,
      desc: 'new Descipriotn',
      services: [ { name: 'http', port: 80, connections: [] } ]
    },
    {
      key: 'key2',
      value: false,
      desc: 'desc 2',
      services: [ { name: 'https', port: 443 }, { name: 'ws', port: 8433 } ]
    }
  ]
}

As you can see, in the services arrays, the "connections" property is missing. What do i need to change in my merge function to keep all property's from the original target object, but also merge everything recursive from the update data?


EDIT: When is swap in the merge.js this part:

   
dst[key] = src[key].map((item, i) => {
    return Object.assign(dst[key][i], item);
});

with:


src[key].forEach((item, i) => {
    loop(dst[key][i], item);
});

i get the output i want:

{
  name: 'Name1',
  desc: 'New Description',
  config: { foo: 'baz' },
  values: [
    {
      key: 'key1',
      value: true,
      desc: 'new Descipriotn',
      services: [ { name: 'http', port: 80, connections: [] } ]
    },
    {
      key: 'key2',
      value: false,
      desc: 'desc 2',
      services: [
        { name: 'https', port: 443, connections: [] },
        { name: 'ws', port: 8433, connections: [] }
      ]
    }
  ]
}

I let this question open, perhaps someone has a better solution.
Answers are welcome!

CodePudding user response:

I think this is what you're looking for. It loops through each property of the object and determines how to merge it. If the current property is an array, it will map that array into an array of merged objects by index. If it's an object, it will recursively call the merge function to return the merged object, and if it's a non-object we can simply treat it like a normal Object.assign(obj1, obj2), where the value from obj2 overrides the value from obj1.

Let me know if you have any questions!

const data = {
name: "Name1",
desc: "Description",
config: {
    foo: "bar"
},
values: [{
    key: "key1",
    value: null,
    desc: "desc 1",
    services: [{
        name: "http",
        port: 80,
        connections: []
    }]
}, {
    key: "key2",
    value: true,
    desc: "desc 2",
    services: [
        {
            name: "http",
            port: 80,
            connections: []
        }, {
            name: "ws",
            port: 8080,
            connections: []
        }
    ]
}]
};


// update object coems from mongodb via change stream
const update = {
desc: "New Description",
config: {
    foo: "baz"
},
values: [{
    key: "key1",
    value: true,
    desc: "new Descipriotn",
}, {
    key: "key2",
    value: false,
    services: [{
        name: "https",
        port: 443
    }, {
        name: "ws",
        port: 8433
    }]
}]
};

function mergeObjs (sourceObj, updateObj) {
  return Object.entries(updateObj).reduce((res, [currKey, currValue]) => {
    if (Array.isArray(currValue)) {
      let sourceArray = sourceObj[currKey] || [];
      res[currKey] = currValue.map((valObj, index) => {
        // this is simply merging on index, but if you wanted a "smarter" merge, you could look up
        // the sourceObj by a specific key with sourceArray.find(...)
        return mergeObjs(sourceArray[index] || {}, valObj)
      });
    } else if (typeof currValue === 'object' && currValue !== null) {
      res[currKey] = mergeObjs(sourceObj[currKey] || {}, currValue)
    } else {
      res[currKey] = currValue;
    }
    return res;
  }, sourceObj);
}

console.log(mergeObjs(data, update));

  • Related