danielaparker / jsoncons

A C++, header-only library for constructing JSON and JSON-like data formats, with JSON Pointer, JSON Patch, JSON Schema, JSONPath, JMESPath, CSV, MessagePack, CBOR, BSON, UBJSON
https://danielaparker.github.io/jsoncons
Other
697 stars 160 forks source link

Set json value for nonexistent path in jsonpath #496

Closed PragmaTwice closed 4 months ago

PragmaTwice commented 5 months ago

First and foremost, I want to express my gratitude. Apache Kvrocks consistently utilizes jsoncons to offer support for JSON data structures to users. We find the development experience with jsoncons outstanding! Many thanks to the authors for your hard work.

Describe the proposed feature As implemented in Redis, we want to set json values for some user-provided nonexistent jsonpath, e.g.

Seems that in the jsonpointer extension in jsoncons, there's some utility to add value to a nonexistent location, i.e. create_if_missing parameter in https://github.com/danielaparker/jsoncons/blob/master/doc/ref/jsonpointer/replace.md, but this feature seems missing in jsonpath.

What other libraries (C++ or other) have this feature? RedisJSON supports this feature.

Include a code fragment with sample data that illustrates the use of this feature

Maybe just like jsonpath, we can also add a parameter like create_if_missing, e.g.

jsonpath::json_replace(path, []{ ... }, /* create_if_missing = */ true);
danielaparker commented 5 months ago

Thanks for the suggestion. We'll look into it, but it could take awhile to get to it.

danielaparker commented 5 months ago

Suppose your data is

{
    "books": [
        {
            "author": "Nigel Rees",
            "price": 8.95,
            "title": "Sayings of the Century"
        },
        {
            "author": "Evelyn Waugh",
            "price": 11.2,
            "title": "Sword of Honour"
        }
    ]
}

and your operation is

jsonpath::assign(data, "$.books[2].price", json{ 13.5 });

What would you expect the result to be?

This?

{
    "books": [
        {
            "author": "Nigel Rees",
            "price": 8.95,
            "title": "Sayings of the Century"
        },
        {
            "author": "Evelyn Waugh",
            "price": 11.2,
            "title": "Sword of Honour"
        },
        {
            "price": 13.5,
        }
    ]
}

Or an error? (like the jsonpointer functions)

What would RedisJson do?

PragmaTwice commented 5 months ago

Hi @danielaparker , thank you for your reply and following up.

I've checked the behavior in RedisJSON.

Basically, I think the only allowed nonexistent path is a nonexistent object key on an existent path, e.g.

So in your example, the write operation will be a no-op in Redis (or say, an error).

danielaparker commented 5 months ago

Okay, I've add the following free function to the jsonpath library (on master),

template<class Json>
std::pair<Json*,bool> assign(Json& root_value, const basic_json_location<Json::char_type>& location, 
    Json&& value, bool create_if_missing = false);   (since 0.174.0)

Attempts to assign a value at the specified location. If create_if_missing is true, creates key-object pairs when an object key is missing. Returns a std::pair<Json*,bool>, the bool component is true if the assignment took place and false if not, if true, the Json* component points to the value that was assigned or inserted.

It can be used as follows,

    std::string json_string = R"(
{"books": [ 
    { "category": "reference",
      "author": "Nigel Rees",
      "title": "Sayings of the Century",
      "price": 8.95
    },
    { "category": "fiction",
      "author": "Evelyn Waugh",
      "title": "Sword of Honour"
    }
  ] 
}
    )";

    json doc = json::parse(json_string);

    jsonpath::json_location loc = jsonpath::json_location::parse("$.books[1].kindle.price");

    auto result1 = jsonpath::assign(doc, loc, json{ 13.5 }, false);
    assert(!result1.second);  // assignment fails

    auto result2 = jsonpath::assign(doc, loc, json{ 13.5 }, true);
    assert(result2.second);  // assignment succeeds

    std::cout << pretty_print(doc) << "\n";

Output:

{
    "books": [
        {
            "author": "Nigel Rees",
            "category": "reference",
            "price": 8.95,
            "title": "Sayings of the Century"
        },
        {
            "author": "Evelyn Waugh",
            "category": "fiction",
            "kindle": {
                "price": 13.5
            },
            "title": "Sword of Honour"
        }    
    ]    
}
PragmaTwice commented 5 months ago

Great! Thank you for your quick action!

danielaparker commented 4 months ago

Renamed to be consistent with jsonpointer::replace, see jsonpath::replace