【问题标题】:Javascript update items in array without overriding the array, update only the object itemsJavascript更新数组中的项目而不覆盖数组,仅更新对象项目
【发布时间】:2023-02-13 03:09:00
【问题描述】:

我在更新数组中的项目时遇到问题。

我通过更改流、更新的集合项从 mongodb 接收。 这些对象需要与包含数组的现有对象合并。

以下示例说明了核心问题是什么(最小化):

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);

Object.assign 的问题是,原始数据中的.values 数组被覆盖而不是合并。

我需要一个合并每个对象的函数,无论对象是否在数组中。这必须递归工作,无论嵌套如何。

我想出了一个看起来像这样的函数:合并.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);

}

最小可重现示例:

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));

合并函数的问题是,当一个属性存在于像connection这样的数组中的原始对象中时,更新目标中不存在该属性。

输出:

{
  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 } ]
    }
  ]
}

如您所见,在服务数组中,缺少“连接”属性。 我需要在我的合并函数中更改什么以保留原始目标对象的所有属性,同时合并更新数据中的所有递归内容?


编辑:什么时候在 merge.js 这部分交换:

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

和:


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

我得到我想要的输出:

{
  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: [] }
      ]
    }
  ]
}

编辑/发现错误:

使用给定的输入(添加了充满字符串的数组),它会产生一个奇怪的输出并将字符串转换为一个对象。

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
        }]
    }],
    labels: [
        "manufacturer=shelly",
        "foo=bar",
        "foo=baz"
    ]
};

Output:
```js
{
  name: 'Name1',
  desc: 'New Description',
  config: { foo: 'baz' },
  values: [
    {
      key: 'key1',
      value: true,
      desc: 'new Descipriotn',
      services: [Array]
    },
    { key: 'key2', value: false, desc: 'desc 2', services: [Array] }
  ],
  labels: [
    {
      '0': 'm',
      '1': 'a',
      '2': 'n',
      '3': 'u',
      '4': 'f',
      '5': 'a',
      '6': 'c',
      '7': 't',
      '8': 'u',
      '9': 'r',
      '10': 'e',
      '11': 'r',
      '12': '=',
      '13': 's',
      '14': 'h',
      '15': 'e',
      '16': 'l',
      '17': 'l',
      '18': 'y'
    },
    {
      '0': 'f',
      '1': 'o',
      '2': 'o',
      '3': '=',
      '4': 'b',
      '5': 'a',
      '6': 'r'
    },
    {
      '0': 'f',
      '1': 'o',
      '2': 'o',
      '3': '=',
      '4': 'b',
      '5': 'a',
      '6': 'z'
    }
  ]
}

它应该保留数组字符串并且不要弄乱它们。

【问题讨论】:

    标签: javascript node.js arrays merge


    【解决方案1】:

    我想这就是你要找的。它循环遍历对象的每个属性并确定如何合并它。如果当前属性是一个数组,它会将该数组映射到一个由索引合并的对象组成的数组中。如果它是一个对象,它将递归调用合并函数以返回合并后的对象,如果它是一个非对象,我们可以简单地将其视为普通的Object.assign(obj1, obj2),其中 obj2 的值覆盖 obj1 的值。

    如果您有任何问题,请告诉我!

    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));

    【讨论】:

    • 非常感谢!该功能并没有像我想的那样 100% 工作,如果对数组和对象进行更深入的测试,一些信息就会丢失。对于我的第一个用例,但绝对完美!你能详细说明一下你是如何编写这个函数的吗?我也想尝试更好地理解递归函数,Object.entries & Array.reduce 不是问题,我更感兴趣的是您在编写此函数时的“思路”。递归函数对我来说是一场噩梦,我想提高自己。
    • @Marc 简而言之,它实际上只是在考虑如何合并每种类型的属性。我从数组开始,因为我知道这是问题所在——我们不想覆盖整个数组,或者数组中的每个完整对象,所以我们用合并的对象填充一个数组。下一个案例是处理对象,在那种情况下,我只想递归调用,因为这是 mergeObjs 函数的目的。最后,任何非数组和非对象的属性都只是用新值写入,然后冒泡返回到之前的调用堆栈。很难在这些 cmets 中解释更多
    • 这对我来说已经足够了。谢谢 :) 我认为剩下的就是练习了。您有任何可以推荐递归的文档或书籍吗?
    • @Marc Kyle Simpson 的 Functional-Light-JS Chapter 8 对不同类型的递归以及如何在学术“斐波那契数列”练习之外使用它们进行了非常详尽的解释。我已经通读了几遍,除此之外,它只是伴随着大量的练习和修补。最终它会点击并且非常直观,尽管一开始它看起来像是黑魔法哈哈
    • 我在函数中发现了一个错误。它将字符串数组转换为对象数组。请参阅我编辑的问题中的“编辑/发现错误”部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 2017-12-05
    • 1970-01-01
    • 1970-01-01
    • 2015-11-20
    相关资源
    最近更新 更多