在JavaScript数组中查找和修改深度嵌套对象

3
我有一个对象数组,长度和深度均可变。我需要通过其id查找并修改该对象在数组中的位置。使用lodash或纯js是否有有效的方法来实现这一点?
我曾尝试创建一个索引数组,以便访问目标对象,但是构造表达式来访问这些索引所对应的对象似乎过于复杂/不必要。
编辑1:感谢所有回复。我将尝试更加具体地说明问题。我正在通过以下方式查找要修改的对象位置。parents是每个父对象的id数组,祖先可能是这个数组的更好名称。costCenters是包含我想要修改的对象的对象数组。此函数递归并返回一个导致我想要修改的对象的索引数组。
var findAncestorsIdxs = function(parents, costCenters, startingIdx, parentsIdxs) {
            var idx = startingIdx ? startingIdx : 0;
            var pidx = parentsIdxs ? parentsIdxs : [];

            _.each(costCenters, function(cc, ccIdx) {
                if(cc.id === parents[idx]) {
                    console.log(pidx);
                    idx = idx + 1;
                    pidx.push(ccIdx);
                    console.log(pidx);
                    pidx = findAncestorsIdx(parents, costCenters[ccIdx].children, idx, pidx);
                }
            });
            return pidx;
        }; 

现在有了这个索引数组,我该如何定位和修改我想要的确切对象?我尝试过使用ancestors作为索引数组,costCenters作为包含要被修改对象的数组,并将parent作为新值分配给目标对象。
var setParentThroughAncestors = function(ancestors, costCenters, parent) {
            var ccs = costCenters;
            var depth = ancestors.length;
            var ancestor = costCenters[ancestors[0]];
            for(i = 1; i < depth; i++) {
                ancestor = ancestor.children[ancestors[i]];
            }
            ancestor = parent;
            console.log(ccs);
            return ccs;
        };

这显然只返回未修改的costCenters数组,那么我能想到的唯一另一种定位该对象的方法是构建类似于myObjects[idx1].children[2].grandchildren[3].ggranchildren[4].something = newValue的表达式。这是唯一的方法吗?如果是,那么最好的方法是什么?


你需要递归迭代所有对象,并与你要查找的ID进行比较。 - Felix Kling
每个数组对象都有一个唯一的ID。我想通过该ID找到给定的对象,然后更改该对象。 - Mark
您可以直接将值分配给对象。例如 if (obj.id === idToFind) { obj.prop = newValue; } - Felix Kling
1
请参见访问/处理(嵌套)对象,数组或JSON。它有一个关于递归的部分。 - Felix Kling
1
你现在陈述的问题方式预设了一种特定的、难以实现的解决方案,即返回某种“索引”到一个对象中,然后可以用它来引用对象内部的某些内容。但是这样的东西并不存在。你最好设计你的解决方案,使你传递一个回调函数,在找到感兴趣的子对象时调用它来处理修改。 - user663031
显示剩余5条评论
6个回答

18
你可以使用JSON.stringify来实现此功能。它提供了一个回调函数,用于访问每个键/值对(在任何深度),并具有跳过或替换的能力。
下面的函数返回一个函数,该函数搜索具有指定ID的对象,并在其上调用指定的转换回调:
function scan(id, transform) {
  return function(obj) {
    return JSON.parse(JSON.stringify(obj, function(key, value) {
      if (typeof value === 'object' && value !== null && value.id === id) {
        return transform(value);
      } else {
        return value;
      }
  }));
}

如果问题陈述如下,您有一个对象数组,以及每个对象中包含要修改的并行id数组,并且有一个变换函数数组,那么只需要将上述内容封装即可。
for (i = 0; i < objects.length; i++) {
    scan(ids[i], transforms[i])(objects[i]);
}

由于JSON.stringify的限制,如果对象中存在循环引用,则此方法将失败,并且会省略函数、正则表达式和符号键属性(如果您关心的话)。有关更多信息,请参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_native_JSON#The_replacer_parameter

4
еҘҪзҡ„пјҒжҲ‘дёҚзҹҘйҒ“JSON.stringifyеҸҜд»ҘжҺҘеҸ—еӣһи°ғеҮҪж•°гҖӮ - Hrishi
1
这是非常有用的信息。我希望在之前我不会尝试使用redux-persist转换器来遍历对象树,只为寻找日期字符串并将其转换为日期对象所花费的令人尴尬的长时间! - Eric G
太棒了!加一分是因为你解释了这种方法的局限性! - Master of Ducks

2
正如Felix Kling所说,您可以递归地迭代所有对象。
// Overly-complex array
var myArray = {
    keyOne: {},
    keyTwo: {
        myId: {a: '3'}
    }
};
var searchId = 'myId', // Your search key
    foundValue, // Populated with the searched object
    found = false; // Internal flag for iterate()

// Recursive function searching through array
function iterate(haystack) {
    if (typeof haystack !== 'object' || haystack === null) return; // type-safety
    if (typeof haystack[searchId] !== 'undefined') {
        found = true;
        foundValue = haystack[searchId];
        return;
    } else {
        for (var i in haystack) {
            // avoid circular reference infinite loop & skip inherited properties
            if (haystack===haystack[i] || !haystack.hasOwnProperty(i)) continue;

            iterate(haystack[i]);
            if (found === true) return;
        }
    }
}

// USAGE / RESULT
iterate(myArray);
console.log(foundValue); // {a: '3'}
foundValue.b = 4; // Updating foundValue also updates myArray
console.log(myArray.keyTwo.myId); // {a: '3', b: 4}

所有JS对象赋值都是按引用传递的。请查看此处以获取有关对象的完整教程。
编辑:感谢@torazaburo对更好代码的建议。

1
你直接修改了foundValue,因为它只是myArray的引用。请查看我上面更新的“RESULT”部分。 - NicolaeS
1
好的,我明白了,谢谢。我会尝试一下,看看效果如何。 - Mark
@torazaburo:typeof 总是返回一个字符串。它永远不会返回 null"null" - Felix Kling
1
@NicolaeS:typeof null也会返回"object"。你需要像这样写:if (!haystack || typeof haystack !== 'object') - Felix Kling
1
抱歉,我的意思是 haystack !== null - user663031
显示剩余5条评论

1
如果每个对象都有一个存储其他嵌套对象的相同名称属性,您可以使用:https://github.com/dominik791/obj-traverse findAndModifyFirst() 方法应该可以解决您的问题。第一个参数是根对象,而不是数组,因此您首先应该创建它。
var rootObj = {
  name: 'rootObject',
  children: [
    {
      'name': 'child1',
       children: [ ... ]
    },
    {
       'name': 'child2',
       children: [ ... ]
    }
  ]
};

然后使用findAndModifyFirst()方法:
findAndModifyFirst(rootObj, 'children', { id: 1 }, replacementObject)

replacementObject 是任何应该替换具有 id 等于 1 的对象的对象。

您可以使用演示应用程序尝试它: https://dominik791.github.io/obj-traverse-demo/


0
这是一个广泛使用lodash的示例。它使您能够根据其键或值转换深度嵌套的值。
const _ = require("lodash")
const flattenKeys = (obj, path = []) => (!_.isObject(obj) ? { [path.join('.')]: obj } : _.reduce(obj, (cum, next, key) => _.merge(cum, flattenKeys(next, [...path, key])), {}));


const registrations = [{
  key: "123",
  responses:
  {
    category: 'first',
  },
}]


function jsonTransform (json, conditionFn, modifyFn) {

  // transform { responses: { category: 'first' } } to { 'responses.category': 'first' }
  const flattenedKeys = Object.keys(flattenKeys(json));

  // Easily iterate over the flat json
  for(let i = 0; i < flattenedKeys.length; i++) {
    const key = flattenedKeys[i];
    const value = _.get(json, key)

    // Did the condition match the one we passed?
    if(conditionFn(key, value)) {

      // Replace the value to the new one    
      _.set(json, key, modifyFn(key, value)) 
    }
  }

  return json
}

// Let's transform all 'first' values to 'FIRST'
const modifiedCategory = jsonTransform(registrations, (key, value) => value === "first", (key, value) => value = value.toUpperCase())

console.log('modifiedCategory --', modifiedCategory)
// Outputs: modifiedCategory -- [ { key: '123', responses: { category: 'FIRST' } } ]

0

最近我编写了这段代码,用于满足我在后端(我的后端是Rails)需要以下形式的键:

first_name

我的前端是React,所以键值(keys)看起来像:

firstName

而这些键几乎总是深度嵌套的:

user: {
  firstName: "Bob",
  lastName: "Smith",
  email: "bob@email.com"
}

成为:

user: {
  first_name: "Bob",
  last_name: "Smith",
  email: "bob@email.com"
}

这里是代码

function snakeCase(camelCase) {
  return camelCase.replace(/([A-Z])/g, "_$1").toLowerCase()
}

export function snakeCasedObj(obj) {
  return Object.keys(obj).reduce(
    (acc, key) => ({
      ...acc,
      [snakeCase(key)]: typeof obj[key] === "object" ? snakeCasedObj(obj[key]) : obj[key],
    }), {},
  );
}

随意更改转换为任何对您有意义的内容!


0

我也需要修改深层嵌套对象,但是没有找到合适的工具。因此,我自己开发了一个并将其发布到了npm。

https://www.npmjs.com/package/find-and

这个小型的 [TypeScript-friendly] 库可以帮助以 lodash 的方式修改嵌套对象。例如,

var findAnd = require("find-and");

const data = {
  name: 'One',
  description: 'Description',
  children: [
    {
      id: 1,
      name: 'Two',
    },
    {
      id: 2,
      name: 'Three',
    },
  ],
};

findAnd.changeProps(data, { id: 2 }, { name: 'Foo' });

输出

{
  name: 'One',
  description: 'Description',
  children: [
    {
      id: 1,
      name: 'Two',
    },
    {
      id: 2,
      name: 'Foo',
    },
  ],
}

https://runkit.com/embed/bn2hpyfex60e

希望这能帮助到其他人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接