在javascript数组中查找并修改深层嵌套对象

时间:2014-08-29 13:25:31

标签: javascript lodash

我有一个对象数组,可以是任何长度和任何深度。我需要能够通过其id找到一个对象,然后在数组中修改该对象。有没有一种有效的方法可以用lodash或纯js来做到这一点?

我以为我可以创建一个导致该对象的索引数组,但构造表达式以使用这些索引访问该对象似乎过于复杂/不必要

EDIT1;感谢你的所有回复我会尝试更具体。我目前正在寻找我试图修改的对象的位置。 parents是目标对象拥有的每个父级的id数组。祖先可能是这个数组的更好名称。 costCenters是包含我要修改的对象的对象数组。这个函数recurses并返回一个索引数组,这些索引导致我想要修改的对象

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]这样的表达式。 something = newValue。这是唯一的方法吗?如果是这样,最好的方法是什么?

6 个答案:

答案 0 :(得分:12)

您可以使用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;
      }
  }));
}

如果问题已经陈述,你有一个对象数组,每个对象的并行数组,其中包含要修改的对象,以及一个转换函数数组,那么它只是包装上面的问题如

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

答案 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提供更好代码的建议。

答案 2 :(得分: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/

答案 3 :(得分: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' } } ]

答案 4 :(得分:0)

我也需要修改深层嵌套的对象,但没有找到可以接受的工具。然后,我将其制成并将其推送到npm。

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

这个小巧的[TypeScript友好]库可以帮助以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

希望这可以帮助其他人。

答案 5 :(得分:0)

我最近编写此代码是为了做到这一点,因为我的后端是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],
    }), {},
  );
}

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