如何更新 Javascript 中的深层嵌套对象?
how to update a deeply nested object in Javascript?
我有一个 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"
的对象,我可以从一个对象中获取它,并将 id 作为类型 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": []
}
]
}
这看起来像一个树遍历问题。这是一种无需递归即可使用 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" />
我在控制台中尝试了这个,它做了你想要的(基于提供的 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)
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}
我有一个 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"
的对象,我可以从一个对象中获取它,并将 id 作为类型 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": []
}
]
}
这看起来像一个树遍历问题。这是一种无需递归即可使用 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" />
我在控制台中尝试了这个,它做了你想要的(基于提供的 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)
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}