【问题标题】:find and modify deeply nested object in javascript array在 javascript 数组中查找和修改深度嵌套的对象
【发布时间】:2014-08-29 13:25:30
【问题描述】:

我有一个可以是任意长度和任意深度的对象数组。我需要能够通过其 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;
        }; 

现在有了这个索引数组,我如何定位和修改我想要的确切对象?我试过这个,祖先是索引数组,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] 这样的表达式。东西=新价值。这是唯一的方法吗?如果是这样,最好的方法是什么?

【问题讨论】:

  • 您必须递归地遍历所有对象并与您要查找的 ID 进行比较。
  • 你说你有一个对象数组。 JavaScript 中的数组是索引的,而不是关联的,所以大概你的意思是通过索引而不是 ID 来查找对象。否则,请澄清。
  • 数组中的每个对象都有一个唯一的 id。我想通过该 id 找到给定的对象,然后更改该对象
  • Access / process (nested) objects, arrays or JSON。它有一个关于递归的部分。
  • 你现在陈述问题的方式预设了一个特定的、尴尬到不可能的解决方案,即将某种“索引”返回到一个对象中,然后可以用来引用物体内部的东西。但是没有这样的动物。您最好设计您的解决方案,以便您传递一个回调,当找到感兴趣的子对象时调用该回调来处理修改。

标签: javascript lodash


【解决方案1】:

您可以为此使用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 了解更多信息。

【讨论】:

  • 不错!不知道JSON.stringify 可以接受回调。
  • 这是非常有用的信息。我希望在我花了很长时间尝试使用 redux-persist 转换器遍历对象树以寻找日期字符串以转换为 Date 对象之前找到它!
  • 太棒了! +1 也用于解释这种方法的局限性!
【解决方案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 对象分配在 JS 中作为引用传递。有关对象的完整教程,请参阅 this :)

编辑:感谢@torazaburo 提出更好代码的建议。

【讨论】:

  • 感谢您的回复,但这并没有告诉我如何修改找到的对象
  • 您直接修改foundValue,因为它只是对myArray 的引用。请参阅上面我更新的“结果”部分。
  • 好的,谢谢,我会试一试,看看效果如何
  • @NicolaeS: typeof null 也返回 "object"。你会想要类似if (!haystack || typeof haystack !== 'object')
  • 对不起,我的意思是haystack !== null
【解决方案3】:

如果每个对象都有存储其他嵌套对象的同名属性,则可以使用: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/

【讨论】:

    【解决方案4】:

    这是一个广泛使用 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' } } ]
    

    【讨论】:

      【解决方案5】:

      我也需要修改深度嵌套的对象,但没有找到可以接受的工具。然后我做了这个并将其推送到 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

      希望这可以帮助其他人。

      【讨论】:

        【解决方案6】:

        我最近写了这段代码就是为了做到这一点,因为我的后端是 rails 并且想要这样的键:

        first_name
        

        我的前端是反应,所以键是这样的:

        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],
            }), {},
          );
        }
        

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

        【讨论】:

          猜你喜欢
          • 2018-03-01
          • 2018-04-13
          • 2018-02-14
          • 2020-12-18
          • 1970-01-01
          • 2020-12-10
          • 2023-01-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多