JSONPath-Plus / JSONPath

A fork of JSONPath from http://goessner.net/articles/JsonPath/
Other
963 stars 169 forks source link

Is there a recommended way to use JSON Path to set a value? #126

Closed devarda closed 4 years ago

devarda commented 4 years ago

I've had to implement my own reverse logic for a simple case like the following example. Are there any recommended approaches to set via JSON Path?

// say that I have a person object and,
// I want to make sure there will always be name fields, etc.
let given_person = {
    'age'             : 30,
    'email'           : 'abc@example.com',
    // let's add first_name, last_name fields
    'something_deeper': {
        'abc': 1,
        // let's add quantity here
    },
};

// defined an object with "json_path":"value" format,
// made sure it is not a deep object.
let obj1 = {
    '$.first_name'               : 'John',
    '$.last_name'                : 'Doe',
    '$.something_deeper.quantity': 11,
};

// since the above object is not deep, 
// I iterated over and found the field without using this library to set,
// used it for making sure we are overwriting if the value does not exist
// made it handle only simple paths like those above

for (let [k, v] of Object.entries(obj1)) {
    if (JSONPath({path: k, json: given_person, wrap: false}) === undefined) {
        let keys = k.split('.');
        if (keys[0] === '$') keys.shift();
        //we are left with something like ["first_name"] or ["something_deeper", "quantity"]

        // here set using a function that basically does obj[k1][k2][k3] = value
        setDeep(keys, v, given_person);

    }

}

//given_person would now look like
/*
{
    'age' : 30,
    'email' : 'abc@example.com',
    'first_name' : 'John',
    'last_name'  : 'Doe',
    'something_deeper': {
        'abc'   : 1,
        'quantity': 11,
    },
}
*/
brettz9 commented 4 years ago

There is no ability to specify a full jsonpath for setting (though see #124 in case this may be a candidate for standardization).

However, if you can adapt your object of paths to move the final key and value into its own object, you can indicate arbitrary json paths leading up to the key:

          it.only('Setting', function () {
            const expected = {
                age: 30,
                email: 'abc@example.com',
                'something_deeper': {
                    abc: 1,
                    quantity: 11
                },
                first_name: 'John',
                last_name: 'Doe'
            };
            const givenPerson = {
                age: 30,
                email: 'abc@example.com',
                // let's add first_name, last_name fields
                'something_deeper': {
                    abc: 1
                    // let's add quantity here
                }
            };

            // A two-level object of arbitrary JSON paths (minus the final key to modify) mapped to the key-values to modify
            const obj1 = {
                $: {
                    'first_name': 'John',
                    'last_name': 'Doe'
                },
                '$.something_deeper': {
                    quantity: 11
                }
            };

            Object.entries(obj1).forEach(([path, valuesToSet]) => {
                jsonpath({
                    json: givenPerson,
                    path,
                    wrap: false,
                    callback (obj) {
                        Object.entries(valuesToSet).forEach(([key, val]) => {
                            obj[key] = val;
                        });
                    }
                });
            });
            const result = givenPerson;
            assert.deepEqual(result, expected);
        });

Closing as I think this should address, though feel free to comment further as needed.