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
726 stars 164 forks source link

jsonpath: evaluation throws on json containing json_const_pointer #554

Open mosswaldcognex opened 3 days ago

mosswaldcognex commented 3 days ago

Calling jsonpath_expression::evaluate() on a JSON that contains a json_const_pointer to a valid second JSON throws "Index on non-array value not supported". The reason seems to be a non-const call to basic_json::at[int i] when selecting an array object.

This behavior exist in the latest release 0.178.0. I used release 0.170.2 before which did not show this exception. There everything worked fine. The attached example_code.txt shows how to reproduce the error.

danielaparker commented 3 days ago

Yes, in 0.172.0 there were some changes to jsonpath::make_expression, basically to support creating expressions that would allow update operations on the root JSON value, in addition to select. My goal was to make these changes entirely transparent to users, but it seems not to be the case for root json values containing json_const_pointer elements.

You can still run this query by changing the template parameter in make_expression from jsoncons::json to const jsoncons::json, viz,

#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
#include <iostream>

int main()
{
    jsoncons::json root_works = jsoncons::json::parse(R"(
{
    "books":
    [
        {
            "category": "fiction",
            "title" : "The Night Watch",
            "author" : "Sergei Lukyanenko",
            "price" : 23.58
        },
        {
            "category": "fiction",
            "title" : "The Comedians",
            "author" : "Graham Greene",
            "price" : 21.99
        }
    ]
}
)");

    jsoncons::json nested_json = jsoncons::json::parse(R"(
[
    {
        "category": "fiction",
        "title" : "The Night Watch",
        "author" : "Sergei Lukyanenko",
        "price" : 23.58
    },
    {
        "category": "fiction",
        "title" : "The Comedians",
        "author" : "Graham Greene",
        "price" : 21.99
    }
]
)");

    jsoncons::json now_works = root_works;
    now_works["books"] = jsoncons::json(jsoncons::json_const_pointer_arg, &nested_json);

    auto expr = jsoncons::jsonpath::make_expression<const jsoncons::json>("$.books[?(@.price > avg($.books[*].price))].title");

    try
    {
        jsoncons::json result_works = expr.evaluate(root_works);
        jsoncons::json result_now_works = expr.evaluate(now_works);

        std::cout << result_now_works << "\n";
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << "\n";
    }
}

Output:

["The Night Watch"]

There's a bit of an issue with json_const_pointer_arg because the root json value becomes half non-const, and half const. The compiler doesn't prevent you from calling functions like insert_or_assign on the root, but the contained json_const_pointer elements will throw on any except const function calls. It puts some burden on the user.

The next version (0.179.0) will support a json value containing non-const references to other json values, and you'll be able to write it as follows:


    jsoncons::json now_works = root_works;
    now_works["books"] = jsoncons::json(jsoncons::json_reference_arg, nested_json);

    auto expr = jsoncons::jsonpath::make_expression<jsoncons::json>("$.books[?(@.price > avg($.books[*].price))].title");

    try
    {
        jsoncons::json result_works = expr.evaluate(root_works);
        jsoncons::json result_now_works = expr.evaluate(now_works);

        std::cout << result_now_works << "\n";
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << "\n";
    }

json_reference_arg is currently supported on master. Unlike with json_const_pointer_arg, all json functions and operators, both accessors and mutators, can be called on a json value containing json_reference_arg elements. Mutators will mutate the referenced elements. And if you don't want mutation, you can always take a const reference to the value and work with that.

mosswaldcognex commented 2 days ago

Many thanks for the detailed explanation! Sorry that I did not immediately recognize the change in 0.172.0. The problem is therefore solved for me. I will test the new value type json_reference_arg at the next opportunity.