skip to Main Content

I need to create a function in JavaScript that allows me to set values in nested objects using dynamic path strings or arrays. The path strings may include indices to specify the exact location where the value should be set. Additionally, the path strings can include "[]" notation to push values into arrays.

For instance, given an object like:

const obj = { foo: { bar: [] } };

I want to set a value at the path "foo.bar[][2]" or "foo.bar[2][]".

The function should be able to handle both string and array representations of the path.

Here’s an example of what I’m looking for:

setValue(obj, 'foo.bar[][2]', 'value');
console.log(obj); // Output: { foo: { bar: [[ <2 empty items>, 'value' ] ] } }

setValue(obj, 'foo.bar[2][]', 'value');
console.log(obj); // Output: { foo: { bar: [ <2 empty items> , [ 'value' ] ] } }

How can I implement such a function efficiently in JavaScript?

2

Answers


  1. You would have to create your own mixin to split the path, retrieve the existing value, and push a new value.

    You will need a bit of RegExp-fu to check the incoming path.

    const state = { foo: { bar: [] } }
    
    _.mixin({
      setValue: function(obj, path, val) {
        let match, full, subpath, index, arr;
        
        // Check for: [][INDEX]
        match = path.match(/(.+)[][(d+)]$/);
        if (match) {
          [full, subpath, index] = match, arr = _.get(obj, subpath, []);
          arr[+index] = val; // Assign value to index
          _.set(obj, subpath, [arr]); // Set as sub-array
          return;
        }
        
        // Check for: [INDEX][]
        match = path.match(/(.+)[(d+)][]$/);
        if (match) {
          [full, subpath, index] = match, arr = _.get(obj, subpath, []);
          arr[+index] = [val]; // Assign sub-array to index
           _.set(obj, subpath, arr); // Set as existing array
           return;
        }
      }
    });
    
    const scenarioA = _.cloneDeep(state);
    _.setValue(scenarioA, 'foo.bar[][2]', 'value');
    console.log(scenarioA); // { foo: { bar: [[, , "value"]] }}
    
    const scenarioB = _.cloneDeep(state);
    _.setValue(scenarioB, 'foo.bar[2][]', 'value');
    console.log(scenarioB); // { foo: { bar: [, , ["value"]] }}
    .as-console-wrapper { top: 0; max-height: 100% !important; }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
    Login or Signup to reply.
  2. We could take the lodash-style set function, but modify it to deal with [] as a an indication to push a value to an array (if it is an array).

    Drawing on the set function provided in this answer, it could be this:

    function setValue(obj, path, value) {
        if (Object(obj) !== obj) return obj;
        if (!Array.isArray(path)) path = path.toString().match(/[^.[]]+|(?<=[)(?=])/g) || []; 
        const a = path.slice(0,-1).reduce((a, c, i) => {
             if (Array.isArray(a) && !c) c = a.length;
             return Object(a[c]) === a[c] 
                 ? a[c] 
                 : a[c] = !path[i+1] || Math.abs(path[i+1])>>0 === +path[i+1] ? [] : {}
        }, obj);
        let c = path.at(-1);
        if (Array.isArray(a) && !c) c = a.length;
        a[c] = value;
        return obj;
    }
    
    // Demo
    const obj = { foo: { bar: [] } };
    setValue(obj, 'foo.bar[][2]', 'value');
    console.log(obj); // Output: { foo: { bar: [[ <2 empty items>, 'value' ] ] } }
    const obj2 = { foo: { bar: [] } };
    setValue(obj2, 'foo.bar[2][]', 'value');
    console.log(obj2); // Output: { foo: { bar: [ <2 empty items> , [ 'value' ] ] } }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search