dchester / jsonpath

Query and manipulate JavaScript objects with JSONPath expressions. Robust JSONPath engine for Node.js.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Struggling with "update or create in array" functionality

pepijnolivier opened this issue · comments

So basically I have a javascript data object that might or might not contain an array entry for the jsonpath.
I would like to use jp.value(data, path, value) and have it update the value in the array if it exists - or create it if it doesn't.

Updating it this way works fine, but something odd is happening during create.

Proof of concept:

import jp from 'jsonpath';

const path = `$.items[?(@.type=='Food')]`;
const value = { "title": "Lasagna", "type": "Food" };

// example one: this works fine
const dataWithExistingFoodType = {
    items: [
        { "title": "Water", type: 'Drinks' },
        { "title": "Spaghetti", type: 'Food' }
    ]
};

jp.value(dataWithExistingFoodType, path, value);
console.log('dataWithExistingFoodType', dataWithExistingFoodType);


// example two: this doesn't work. See output, the jsonpath somehow ends up as as an object key
const dataWithoutExistingFoodType = {
    items: [
        { "title": "Water", type: 'Drinks' }
    ]
};
jp.value(dataWithoutExistingFoodType, path, value);
console.log('dataWithoutExistingFoodType', dataWithoutExistingFoodType);

Output

dataWithExistingFoodType {
  items: [
    { title: 'Water', type: 'Drinks' },
    { title: 'Lasagna', type: 'Food' }
  ]
}
dataWithoutExistingFoodType {
  items: [
    { title: 'Water', type: 'Drinks' },
    "?(@.type=='Food')": { title: 'Lasagna', type: 'Food' }
  ]
}

☝️ You'll see that in the second example, the data is created inside the array... But not exactly with the right jsonpath. Somehow the jsonpath ends up as an object key. Is there any way I can make it so that the data is correctly appended to the array as I would expect it to ?

Thanks for any directions

I am running into the same issue.

I ended up working around the issue by defining the field with just the root array json path (eg $.items rather than $.items[?(@.type=='Food')]) and using a wrapper component to handle the create or update functionality.

It's a workaround, but gets the job done. Just FYI for anyone else struggling with this (@dsdavis4)

I'm having a similar issue trying to set a property in an object which doesn't already exist in the object. So I wrote a function as a workaround. Essentially I initialize each property that doesn't exist in the object before setting the final value. This probably doesn't handle all the edge cases but I think a similar approach could be taken for arrays where you push a new item onto the array.

function setValueOnNewProperty(theObject, newValue) {
    let paths = jp.paths(theObject, path);

    const parsed = jp.parse(path);
    const queue = [];
    let shorterPath = '';

    // find a path that does exist on the object
    while (paths.length === 0) {
        queue.push(parsed.pop());
        shorterPath = jp.stringify(parsed);
        paths = jp.paths(theObject, shorterPath);
    }

    // build up the object to set the value
    while (queue.length > 0) {

        const subPath = queue.pop();
        const newPath = subPath.expression.value;

        // need to initialize every property that doesn't already exist on the object
        const currentValue = jp.value(theObject, shorterPath);
        currentValue[newPath] = {}
        jp.value(theObject, shorterPath, currentValue);

        parsed.push(subPath);
        shorterPath = jp.stringify(parsed);

        // at the end of reconstructing the path -> set the final/new value
        if (queue.length === 0) {
            jp.value(theObject, shorterPath, newValue);
        }
    }
}

If it may be of any use to someone: this is a further work on @drc-nloftsgard workaround that:

  • creates missing property in all elements if path matches an array (i.e. $[*].iDontExist)
  • handles root object (see #72) path $.iDontExist
  • handles if property already exists (it directly assigns the value) $.iExist

`
const JSON_PATH_ROOT = "$";
function setValueOnNewProperty(theObject, path, newValue)
{
let paths = jp.paths(theObject, path);

    const parsed = jp.parse(path);
    const queue = [];
    let shorterPath = '';

    // find a path that does exist on the object
    while (paths.length === 0) {
        queue.push(parsed.pop());
        shorterPath = jp.stringify(parsed);
        paths = jp.paths(theObject, shorterPath);
    }

    // build up the object to set the value
    while (queue.length > 0) {

        const subPath = queue.pop();
        const newPath = subPath.expression.value;

        // need to initialize every property that doesn't already exist on the object
        if (shorterPath === JSON_PATH_ROOT)
        {
            // root, see https://github.com/dchester/jsonpath/issues/72
            jp.value(theObject, "$")[newPath] = {}
        }
        else 
        {
            jp.apply(theObject, shorterPath, function(elem) {
                elem[newPath] = {}
                return elem
            })
        }

        parsed.push(subPath);
        shorterPath = jp.stringify(parsed);

        // at the end of reconstructing the path -> set the final/new value
        if (queue.length === 0) {
            jp.apply(theObject, shorterPath, x => newValue);
            return
        }
    }

    // When property exists
    jp.apply(theObject, path, x => newValue);
}`