find and modify deeply nested object in javascript array

前端 未结 6 1847
情深已故
情深已故 2021-01-06 12:11

I have an array of objects that can be of any length and any depth. I need to be able to find an object by its id and then modify that object within the array. Is there an

相关标签:
6条回答
  • 2021-01-06 12:47

    As Felix Kling said, you can iterate recursively over all objects.

    // 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}
    

    All JS object assignations are passed as reference in JS. See this for a complete tutorial on objects :)

    Edit: Thanks @torazaburo for suggestions for a better code.

    0 讨论(0)
  • 2021-01-06 12:49

    I needed to modify deeply nested objects too, and found no acceptable tool for that purpose. Then I've made this and pushed it to npm.

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

    This small [TypeScript-friendly] lib can help with modifying nested objects in a lodash manner. E.g.,

    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' });
    

    outputs

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

    https://runkit.com/embed/bn2hpyfex60e

    Hope this could help someone else.

    0 讨论(0)
  • 2021-01-06 12:54

    Here's an example that extensively uses lodash. It enables you to transform a deeply nested value based on its key or its value.

    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 讨论(0)
  • 2021-01-06 12:56

    If each object has property with the same name that stores other nested objects, you can use: https://github.com/dominik791/obj-traverse

    findAndModifyFirst() method should solve your problem. The first parameter is a root object, not array, so you should create it at first:

    var rootObj = {
      name: 'rootObject',
      children: [
        {
          'name': 'child1',
           children: [ ... ]
        },
        {
           'name': 'child2',
           children: [ ... ]
        }
      ]
    };
    

    Then use findAndModifyFirst() method:

    findAndModifyFirst(rootObj, 'children', { id: 1 }, replacementObject)
    

    replacementObject is whatever object that should replace the object that has id equal to 1.

    You can try it using demo app: https://dominik791.github.io/obj-traverse-demo/

    0 讨论(0)
  • 2021-01-06 12:59

    I wrote this code recently to do exactly this, as my backend is rails and wants keys like:

    first_name
    

    and my front end is react, so keys are like:

    firstName
    

    And these keys are almost always deeply nested:

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

    Becomes:

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

    Here is the code

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

    Feel free to change the transform to whatever makes sense for you!

    0 讨论(0)
  • 2021-01-06 13:07

    You can use JSON.stringify for this. It provides a callback for each visited key/value pair (at any depth), with the ability to skip or replace.

    The function below returns a function which searches for objects with the specified ID and invokes the specified transform callback on them:

    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;
          }
      }));
    }
    

    If as the problem is stated, you have an array of objects, and a parallel array of ids in each object whose containing objects are to be modified, and an array of transformation functions, then it's just a matter of wrapping the above as

    for (i = 0; i < objects.length; i++) {
        scan(ids[i], transforms[i])(objects[i]);
    }
    

    Due to restrictions on JSON.stringify, this approach will fail if there are circular references in the object, and omit functions, regexps, and symbol-keyed properties if you care.

    See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_native_JSON#The_replacer_parameter for more info.

    0 讨论(0)
提交回复
热议问题