diff --git a/examples/src/jmespath_customer_functions_examples.cpp b/examples/src/jmespath_customer_functions_examples.cpp new file mode 100644 index 0000000000..38cf637205 --- /dev/null +++ b/examples/src/jmespath_customer_functions_examples.cpp @@ -0,0 +1,275 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under Boost license + +#include +#include + +#include +#include +#include +#include +#include + +// When adding custom functions, they are generally placed in their own project's source code and namespace. +namespace myspace +{ + using Json = jsoncons::json; + using JsonReference = const Json &; + using jmespath_errc = jsoncons::jmespath::jmespath_errc; +#define json_object_arg jsoncons::json_object_arg +#define json_array_arg jsoncons::json_array_arg +#define json_const_pointer_arg jsoncons::json_const_pointer_arg + + using function_base = jsoncons::jmespath::detail::jmespath_evaluator::function_base; + using dynamic_resources = jsoncons::jmespath::detail::dynamic_resources; + using static_resources = jsoncons::jmespath::detail::jmespath_evaluator::static_resources; + using parameter = jsoncons::jmespath::detail::jmespath_evaluator::parameter; + using string_type = jsoncons::jmespath::detail::jmespath_evaluator::string_type; + using expression_base = jsoncons::jmespath::detail::jmespath_evaluator::expression_base; + using customer_get_function = jsoncons::jmespath::detail::jmespath_evaluator::static_resources::customer_get_function; + + bool is_integer(JsonReference value) + { + if (value.is() || value.is() || value.is() || value.is()) + { + return true; + } + else + { + return false; + } + } + + JsonReference get_value(JsonReference context, dynamic_resources &resources, const parameter &p) + { + if (p.is_expression()) + { + const auto &expr = p.expression(); + std::error_code ec2; + JsonReference value = expr.evaluate(context, resources, ec2); + // if (value.is_object() || value.is_array()) + // { + // return *resources.create_json(deep_copy(value)); + // } + // else + // { + // return value; + // } + return value; + } + else + { + JsonReference value = p.value(); + return value; + } + } + + class current_date_time_function : public function_base + { + public: + current_date_time_function() : function_base(0) {} + JsonReference evaluate(std::vector &args, dynamic_resources &resources, std::error_code &ec) const override + { + auto now = std::chrono::system_clock::now(); + auto milliseconds = std::chrono::duration_cast(now.time_since_epoch()); + return *resources.create_json(milliseconds.count()); + } + std::string to_string(std::size_t = 0) const override + { + return std::string("current_date_time_function\n"); + } + }; + + class current_index_function : public function_base + { + public: + current_index_function() : function_base(0) {} + JsonReference evaluate(std::vector &args, dynamic_resources &resources, std::error_code &ec) const override + { + size_t index = current_index_function::index; + JsonReference result = *resources.create_json(index); + return result; + } + std::string to_string(std::size_t = 0) const override + { + return std::string("current_index_function\n"); + } + + static thread_local size_t index; + }; + + thread_local size_t current_index_function::index = 0; + + /// @brief generate array,include 4 args:context value,array size (or &expression),&generate expression,default value (or &expression) + class generate_array_function : public function_base + { + public: + generate_array_function() : function_base(4) {} // context, size (or &expression), &expression, default (or &expression) + JsonReference evaluate(std::vector &args, dynamic_resources &resources, std::error_code &ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[2].is_expression())) + { + ec = jmespath_errc::invalid_argument; + return resources.null_value(); + } + + const auto context = args[0].value(); + const auto countValue = get_value(context, resources, args[1]); + const auto &expr = args[2].expression(); + const auto &argDefault = args[3]; + + if (!countValue.is_number()) + { + ec = jmespath_errc::invalid_argument; + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + int count = countValue.template as(); + for (size_t i = 0; i < count; i++) + { + current_index_function::index = i; + std::error_code ec2; + + auto ele = expr.evaluate(context, resources, ec2); + + if (ele.is_null()) + { + auto defaultVal = get_value(context, resources, argDefault); + result->emplace_back(defaultVal); + } + else + { + // result->emplace_back(ele); // ?: It may lead to an abnormal exit. + result->emplace_back(*resources.create_json(deep_copy(ele))); + } + } + current_index_function::index = 0; + + return *result; + } + std::string to_string(std::size_t = 0) const override + { + return std::string("generate_array_function\n"); + } + }; + + class add_function : public function_base + { + public: + add_function() : function_base(2) {} + JsonReference evaluate(std::vector &args, dynamic_resources &resources, std::error_code &ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_value())) + { + ec = jmespath_errc::invalid_argument; + return resources.null_value(); + } + + const auto arg0 = args[0].value(); + const auto arg1 = args[1].value(); + if (!(arg0.is_number() && arg1.is_number())) + { + ec = jmespath_errc::invalid_argument; + return resources.null_value(); + } + + if (is_integer(arg0) && is_integer(arg1)) + { + int64_t v = arg0.template as() + arg1.template as(); + return *resources.create_json(v); + } + else + { + double v = arg0.template as() + arg1.template as(); + return *resources.create_json(v); + } + } + std::string to_string(std::size_t = 0) const override + { + return std::string("add_function\n"); + } + }; + + void init_customer_jmespath_functions() + { + customer_get_function cgf = [](const string_type &name) -> const function_base * + { + static current_date_time_function current_date_time_func; + static current_index_function current_index_func; + static generate_array_function generate_array_func; + static add_function add_func; + + static std::map functions = { + {"current_date_time", ¤t_date_time_func}, + {"current_index", ¤t_index_func}, + {"generate_array", &generate_array_func}, + {"add", &add_func}}; + + auto it = functions.find(name); + + if (it == functions.end()) + { + return nullptr; + } + else + { + return it->second; + } + }; + + static_resources::get_or_set_customer_get_function(cgf, true); + } + +} + +// for brevity +using jsoncons::json; +namespace jmespath = jsoncons::jmespath; + +void jmespath_customer_functions_example() +{ + std::string jtext = R"( + { + "devices": [ + { + "position": 1, + "id": "id-xxx", + "state": 1 + }, + { + "position": 5, + "id": "id-yyy", + "state": 1 + }, + { + "position": 9, + "id": "id-mmm", + "state": 2 + } + ] + } + )"; + + auto expr = jmespath::jmespath_expression::compile("generate_array(devices, `16`, &[?position==add(current_index(), `1`)] | [0], &{id: '', state: `0`, position: add(current_index(), `1`)})"); + + json doc = json::parse(jtext); + + json result = expr.evaluate(doc); + + std::cout << pretty_print(result) << "\n\n"; +} + +int main() +{ + std::cout << "\nJMESPath customer functions examples\n\n"; + myspace::init_customer_jmespath_functions(); + + jmespath_customer_functions_example(); + + std::cout << "\n"; +} diff --git a/include/jsoncons_ext/jmespath/jmespath.hpp b/include/jsoncons_ext/jmespath/jmespath.hpp index edc81bddaa..419fbda9b9 100644 --- a/include/jsoncons_ext/jmespath/jmespath.hpp +++ b/include/jsoncons_ext/jmespath/jmespath.hpp @@ -790,7 +790,7 @@ namespace jmespath { sum += j.template as(); } - return sum == 0 ? resources.null_value() : *resources.create_json(sum/arg0.size()); + return arg0.size() == 0 ? resources.null_value() : *resources.create_json(sum / arg0.size()); } }; @@ -3212,6 +3212,17 @@ namespace jmespath { static_resources(static_resources&& expr) = default; static_resources& operator=(static_resources&& expr) = default; + typedef std::function customer_get_function; + static const customer_get_function get_or_set_customer_get_function(customer_get_function cgf = nullptr, bool set = false) + { + static customer_get_function customer = nullptr; + if(set){ + customer = cgf; + } + + return customer; + } + const function_base* get_function(const string_type& name, std::error_code& ec) const { static abs_function abs_func; @@ -3271,6 +3282,15 @@ namespace jmespath { {string_type{'t','o','_', 's', 't', 'r','i','n','g'}, &to_string_func}, {string_type{'n','o','t', '_', 'n', 'u','l','l'}, ¬_null_func} }; + + const customer_get_function cgf = get_or_set_customer_get_function(); + if(cgf){ + const function_base *func = cgf(name); + if(func){ + return func; + } + } + auto it = functions_.find(name); if (it == functions_.end()) { @@ -3749,7 +3769,29 @@ namespace jmespath { push_token(token(f), ec); if (ec) {return jmespath_expression();} state_stack_.back() = path_state::function_expression; - state_stack_.emplace_back(path_state::expression_or_expression_type); + // check no-args function + bool is_no_args_func = true; + bool isEnd = false; + for (const char_type *p2_ = p_ + 1; p2_ < end_input_ && !isEnd; ++p2_) + { + + switch (*p2_) + { + case ' ':case '\t':case '\r':case '\n': + break; + case ')': + isEnd = true; + break; + default: + is_no_args_func = false; + isEnd = true; + break; + } + } + if (!is_no_args_func) + { + state_stack_.emplace_back(path_state::expression_or_expression_type); + } ++p_; ++column_; break; @@ -5037,6 +5079,14 @@ namespace jmespath { ec = jmespath_errc::invalid_arity; return; } + if (arg_count == 0) + { + toks.emplace_back(std::move(*it)); + ++it; + output_stack_.erase(it.base(), output_stack_.end()); + output_stack_.emplace_back(token(jsoncons::make_unique(std::move(toks)))); + break; + } if (toks.back().type() != token_kind::literal) { toks.emplace_back(current_node_arg); diff --git a/test/jmespath/input/compliance/functions.json b/test/jmespath/input/compliance/functions.json index d2ec936910..d17a63f924 100644 --- a/test/jmespath/input/compliance/functions.json +++ b/test/jmespath/input/compliance/functions.json @@ -573,7 +573,7 @@ }, { "expression": "not_null()", - "error": "invalid-arity" + "result": null }, { "comment": "function projection on single arg function",