【问题标题】:how to update a deeply nested object in Javascript?如何在 Javascript 中更新深度嵌套的对象?
【发布时间】:2022-01-12 03:32:59
【问题描述】:

我有一个这种类型的 json 对象。

{
    "id": "001",
    "type": "A",
    "value": "aaaaa",
    "data:": {},
    "path": ["001"],
    "children": [
        {
            "id": "002",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "002"],
            "children": []
        },
        {
            "id": "003",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "003"],
            "children": [
                {
                    "id": "00001",
                    "type": "B",
                    "children": []
                }
            ]
        },
        {
            "id": "004",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "004"],
            "children": [
                {
                    "id": "005",
                    "type": "A",
                    "value": "aaaaa",
                    "data:": {},
                    "path": ["001", "004", "005"],
                    "children": []
                },{
                    "id": "005",
                    "type": "A",
                    "value": "aaaaa",
                    "data:": {},
                    "path": ["001", "004", "005"],
                    "children": [
                        {
                            "id": "00002",
                            "type": "B",
                            "children": []
                        }
                    ]
                }
            ]
        },
        {
            "id": "00003",
            "type": "B",
            "children": []
        }
    ]
}

我需要将所有 type: "B" 对象替换为这种(下面提到的)类型的对象,我可以从具有 ids 作为类型 B 的键的对象中获取该类型的对象。这个类型 B 对象可以嵌套在任何地方作为嵌套子数组的第一个孩子或第五个孩子

{
    "id": "002",
    "type": "A",
    "value": "aaaaa",
    "data:": {},
    "children": []
},

我该怎么做?这可以是深度嵌套的,并且没有我们应该事先替换对象的特定位置。所以,我需要遍历整个对象并做到这一点。我应该如何完成它?

编辑

我稍微更新了问题中的代码。除了类型化的 B 对象外,每个对象都有一个嵌套的路径属性。因此,当用另一个对象替换键入的 B 属性时,我还需要在其中添加路径。

例如:id 的路径:“00001”,类型 B 对象应为:[“001”,“003”,“00001”]

编辑: 预期结果

{
    "id": "001",
    "type": "A",
    "value": "aaaaa",
    "data:": {},
    "path": ["001"],
    "children": [
        {
            "id": "002",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "002"],
            "children": []
        },
        {
            "id": "003",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "003"],
            "children": [
                {
                    "id": "002",
                    "type": "A",
                    "value": "aaaaa",
                    "data:": {},
                    "path": ["001", "003", "002"],
                    "children": []
                },
            ]
        },
        {
            "id": "004",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "004"],
            "children": [
                {
                    "id": "005",
                    "type": "A",
                    "value": "aaaaa",
                    "data:": {},
                    "path": ["001", "004", "005"],
                    "children": []
                },{
                    "id": "005",
                    "type": "A",
                    "value": "aaaaa",
                    "data:": {},
                    "path": ["001", "004", "005"],
                    "children": [
                        {
                            "id": "002",
                            "type": "A",
                            "value": "aaaaa",
                            "data:": {},
                            "path": ["001", "004", "005", "002"],
                            "children": []
                        }
                    ]
                }
            ]
        },
        {
            "id": "002",
            "type": "A",
            "value": "aaaaa",
            "data:": {},
            "path": ["001", "002"],
            "children": []
        }
    ]
}

【问题讨论】:

  • 可能是递归函数;具体问题是什么?
  • 谢谢@Kinglish。但在这种情况下,我不知道应该事先在哪里替换它,而不是进入对象中的所有路径。所以,我认为这并不像那个答案中提到的那样简单。
  • @DaveNewton 谢谢。是的。我正在为此实现一个递归函数。但是由于我事先不知道在哪里替换,所以我需要遍历对象中的所有路径。想知道是否有任何其他方法或类似的场景
  • 这是递归部分。

标签: javascript arrays json object nested


【解决方案1】:

lodash如果你不介意的话。

使用cloneDeepWith 克隆整个树并替换特定值。

const data = {"id":"001","type":"A","value":"aaaaa","data:":{},"children":[{"id":"002","type":"A","value":"aaaaa","data:":{},"children":[]},{"id":"003","type":"A","value":"aaaaa","data:":{},"children":[{"id":"00001","type":"B","children":[]}]},{"id":"004","type":"A","value":"aaaaa","data:":{},"children":[{"id":"005","type":"A","value":"aaaaa","data:":{},"children":[]},{"id":"005","type":"A","value":"aaaaa","data:":{},"children":[{"id":"00002","type":"B","children":[]}]}]},{"id":"00003","type":"B","children":[]}]};

const result = _.cloneDeepWith(data, (value) => {
  const newObj = {"id": "002", "type": "A", "value": "---NEW VALUE FOR 'B' TYPE---", "data:": {} };
  return (value.type === 'B') ? { ...value, ...newObj} : _.noop();
});


console.dir(result, { depth: null } );
.as-console-wrapper{min-height: 100%!important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js" integrity="sha512-2iwCHjuj+PmdCyvb88rMOch0UcKQxVHi/gsAml1fN3eg82IDaO/cdzzeXX4iF2VzIIes7pODE1/G0ts3QBwslA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

--- 更新 2---(没有 lodash)

使用局部变量来存储和组合当前路径。

const data = { "id": "001", "type": "A", "value": "aaaaa", "data:": {}, "path": ["001"], "children": [{ "id": "002", "type": "A", "value": "aaaaa", "data:": {}, "path": ["001", "002"], "children": [] }, { "id": "003", "type": "A", "value": "aaaaa", "data:": {}, "path": ["001", "003"], "children": [{ "id": "00001", "type": "B", "children": [] }] }, { "id": "004", "type": "A", "value": "aaaaa", "data:": {}, "path": ["001", "004"], "children": [{ "id": "005", "type": "A", "value": "aaaaa", "data:": {}, "path": ["001", "004", "005"], "children": [] }, { "id": "005", "type": "A", "value": "aaaaa", "data:": {}, "path": ["001", "004", "005"], "children": [{ "id": "00002", "type": "B", "children": [] }] }] }, { "id": "00003", "type": "B", "children": [] }] }

const deepReplace = (obj, prevPath = []) => {
    if (obj.type === 'A') {
      if  (obj.children.length) {
        obj.children = obj.children.map((childObj) => deepReplace(childObj, obj.path))
      }
      return obj;
    };
    if (obj.type === 'B') {
      const id = '002';
      return { id, type: "A", value: "aaaaa",  path: [...prevPath, id],  data: {}, children: []};
    };
 };

console.dir(deepReplace(data), { depth: null });
.as-console-wrapper{min-height: 100%!important; top: 0}

【讨论】:

  • 谢谢,。这行得通。但是,对于 B 类对象,我还需要根据父对象更新属性。这就是我现在想要弄清楚的。
  • @ThidasaPankaja,您能否更新一下您期望的结果。
  • 更新为预期结果
  • 您编辑的答案似乎几乎可以工作。但是对于最后一项(类型为 B,id 0003),它返回 ['001', '004', '005', '00002', '00003'] 作为路径而不是 path: (5) ['001', '00003']
  • @ThidasaPankaja,是的,我看到了问题所在。尝试解决它,稍晚一点。
【解决方案2】:

我在控制台中玩过这个,它做了你想要的(基于提供的 json 数组,将所有“B”设置为“A”类型)。它是一个递归函数,因此对于它在“children”数组中遇到的任何嵌套子元素,它都会在数组中的每个项目上再次调用该函数。


function fixObjects (obj) {
  if (obj["type"] === "B") {
    obj["type"] = "A";
    obj["id"] = "002";
    obj["value"] = "aaaaa";
    obj["data"] = {};
  } 
  if (obj["children"].length > 0) {
     obj["children"].forEach (child => fixObjects (child));
  }
}

fixObjects (_yourArray)

【讨论】:

  • 谢谢。这行得通。但是,对于 B 类对象,我还需要根据父对象更新属性。这就是我现在想要弄清楚的。
【解决方案3】:

这看起来像一个树遍历问题。这是一种无需递归即可使用depth-first search 处理的方法。

正如this 回答中提到的,应尽可能避免递归,因为它需要更多内存并且比迭代实现更难调试。

根据相关调整进行更新

const data = {"id":"001","type":"A","value":"aaaaa","data:":{},"path":["001"],"children":[{"id":"002","type":"A","value":"aaaaa","data:":{},"path":["001","002"],"children":[]},{"id":"003","type":"A","value":"aaaaa","data:":{},"path":["001","003"],"children":[{"id":"00001","type":"B","children":[]}]},{"id":"004","type":"A","value":"aaaaa","data:":{},"path":["001","004"],"children":[{"id":"005","type":"A","value":"aaaaa","data:":{},"path":["001","004","005"],"children":[]},{"id":"005","type":"A","value":"aaaaa","data:":{},"path":["001","004","005"],"children":[{"id":"00002","type":"B","children":[]}]}]},{"id":"00003","type":"B","children":[]}]};

const dfs = () => {
  const stack = [[data, null]];
  
  while(stack.length) {
    const [curr, parent] = stack.pop();
    // check for match on type
    if (curr.type === "B") {
      curr.type = "A";
      curr.id = "002";
      curr.value = "aaaaa";
      curr.data = {};
      curr.path = [...parent?.path.slice() ?? [], "002"];
    }
    curr.children.forEach(child => stack.push([child, curr]));
  }
};

dfs();

const output = document.getElementById("output");
output.innerText = JSON.stringify(data, null, 2);
<pre id="output" />

【讨论】:

  • 谢谢。这行得通。但是,对于 B 类对象,我还需要根据父对象更新属性。这就是我现在想要弄清楚的。
  • @ThidasaPankaja 根据此信息更新了答案。
  • 这太完美了
猜你喜欢
  • 1970-01-01
  • 2018-06-04
  • 1970-01-01
  • 1970-01-01
  • 2021-06-25
  • 1970-01-01
  • 2019-02-03
  • 2022-06-16
  • 2023-01-22
相关资源
最近更新 更多