Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

variant of json_replace() that takes function object #279

Merged
merged 1 commit into from
Oct 26, 2020

Conversation

wbangna
Copy link
Contributor

@wbangna wbangna commented Oct 22, 2020

This makes it possible to change values depending on their previous values.
With json_replace() it is possible to replace a matched value with a new predefined value but it is not possible to rewrite the value depending on the previous value.
Getting the pointers of the matched values allows us to run search-and-replace rules on the old value to generate the new value.

@wbangna wbangna force-pushed the master branch 2 times, most recently from 316b5f0 to 3da4a87 Compare October 22, 2020 12:51
@danielaparker
Copy link
Owner

danielaparker commented Oct 23, 2020

I think the goal for this pull request, to allow access to the vector of pointers, is worthwhile.

But there's an issue with the approach. While typically the pointers in the vector are to values in the original json, there are also cases where the pointers are to instances created during the JSONPath evaluation and stored in a jsonpath_resources<Json> object. This happens in json_query.hpp wherever resources.create_temp is called. An example would be when the query returns the length of an array, or the result of a function call. The problem is that when your json_query_ptr returns, the temporary jsonpath_resources<Json> object is gone, and pointers to instances stored there would be left dangling.

So one possible solution might be

json root;

jsonpath_resources<json> resources;
auto result = json_query_ptr(root, "$.*", resources);

@wbangna
Copy link
Contributor Author

wbangna commented Oct 23, 2020

Thank you for your quick response!
I wasn't aware of the possibility of temporary nodes.
There is one little problem with your suggestion. jsonpath_resources is defined in the detail namespace. So I would need to make an alias to make it available in the API.
An other solution would be to use a callback function that gets called for every matching node.

@danielaparker
Copy link
Owner

danielaparker commented Oct 23, 2020

I like your idea of a callback, and prefer it to my suggestion, because it exposes less implementation detail, and requires only an additional overload for json_replace,

template<class Json, class Op>
void json_replace(Json& root, const typename Json::string_view_type& path, Op op);

with the signature of op being equivalent to

Json fun(const Json& a);

We would need type requirements on the second template parameter Op to disambiguate between this and the original overload.

Your example then becomes

auto op = [](const json& val) {return std::round(val.as<double>() - 1.0);};

json_replace(booklist, "$.store.book[*].price", op);

with op being called for each item in the vector of pointers.

What do you think?

@wbangna
Copy link
Contributor Author

wbangna commented Oct 26, 2020

I tried to implement an overload of json_replace() but I found it pretty hard to distinguish between a callback and all the other possible types. So I wrote a function json_replace_fn() function instead.
I still would prefer to have an overload of json_replace().
My ideas to make this possible would be:

  • make an overload with std::function (comes with a runtime cost)
  • implementing a trait class that detects callable types

I was a little disappointed that there is no such trait implementation already in the standard library. The only thing I found was std::is_function which does not work for callable objects but only for "real" functions.
I could try to combine the trait std::is_function with a trait that detects whether the operator() is implemented.

@danielaparker
Copy link
Owner

danielaparker commented Oct 26, 2020

Regarding your second suggestion, implementing a trait class that detects callable types, try

    template<class FunctionObject, class Arg>
    using
        function_object_t = decltype(std::declval<FunctionObject>()(std::declval<Arg>()));

    template<class FunctionObject, class Arg>
    using
        is_function_object = jsoncons::detail::is_detected<function_object_t, FunctionObject, Arg>;

    template<class Json, class T>
    typename std::enable_if<!is_function_object<T,Json>::value,void>::type
    json_replace(Json& root, const typename Json::string_view_type& path, T&& new_value)
    {
    }

    template<class Json, class Op>
    typename std::enable_if<is_function_object<Op,Json>::value,void>::type
    json_replace(Json& root, const typename Json::string_view_type& path, Op op)
    {
    }

If that works, is_function_object can be moved to jsoncons/detail/more_type_traits.hpp, in namespace jsoncons::detail.

Now it is possible to use json_replace() to rewrite matched
values with a callback function.
@wbangna
Copy link
Contributor Author

wbangna commented Oct 26, 2020

Wow! Thank you, that worked!
I moved the new trait object to jsoncons/detail/more_type_traits.hpp as you whished.

@danielaparker danielaparker merged commit 49aa77f into danielaparker:master Oct 26, 2020
@danielaparker
Copy link
Owner

Thanks for contributing!

@danielaparker danielaparker changed the title variant of json_query() that returns pointers variant of json_replace() that takes function object Oct 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants