Search code examples
javascriptlodash

How to set values in JavaScript objects using dynamic path strings or arrays, with support for pushing values into arrays and specifying indices?


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?


Solution

  • 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' ] ] } }