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

add customer functions for jmespath #560

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
275 changes: 275 additions & 0 deletions examples/src/jmespath_customer_functions_examples.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
// Copyright 2013-2024 Daniel Parker
// Distributed under Boost license

#include <chrono>
#include <thread>

#include <string>
#include <fstream>
#include <cassert>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jmespath/jmespath.hpp>

// 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<Json, JsonReference>::function_base;
using dynamic_resources = jsoncons::jmespath::detail::dynamic_resources<Json, JsonReference>;
using static_resources = jsoncons::jmespath::detail::jmespath_evaluator<Json, JsonReference>::static_resources;
using parameter = jsoncons::jmespath::detail::jmespath_evaluator<Json, JsonReference>::parameter;
using string_type = jsoncons::jmespath::detail::jmespath_evaluator<Json, JsonReference>::string_type;
using expression_base = jsoncons::jmespath::detail::jmespath_evaluator<Json, JsonReference>::expression_base;
using customer_get_function = jsoncons::jmespath::detail::jmespath_evaluator<Json, JsonReference>::static_resources::customer_get_function;

bool is_integer(JsonReference value)
{
if (value.is<int32_t>() || value.is<uint32_t>() || value.is<int64_t>() || value.is<uint64_t>())
{
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<parameter> &args, dynamic_resources &resources, std::error_code &ec) const override
{
auto now = std::chrono::system_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(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<parameter> &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<parameter> &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<int>();
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<parameter> &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<int64_t>() + arg1.template as<int64_t>();
return *resources.create_json(v);
}
else
{
double v = arg0.template as<double>() + arg1.template as<double>();
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<string_type, function_base *> functions = {
{"current_date_time", &current_date_time_func},
{"current_index", &current_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<json>::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";
}
54 changes: 52 additions & 2 deletions include/jsoncons_ext/jmespath/jmespath.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ namespace jmespath {
sum += j.template as<double>();
}

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());
}
};

Expand Down Expand Up @@ -3212,6 +3212,17 @@ namespace jmespath {
static_resources(static_resources&& expr) = default;
static_resources& operator=(static_resources&& expr) = default;

typedef std::function<const function_base *(const string_type &)> 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;
Expand Down Expand Up @@ -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'}, &not_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())
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<function_expression>(std::move(toks))));
break;
}
if (toks.back().type() != token_kind::literal)
{
toks.emplace_back(current_node_arg);
Expand Down
2 changes: 1 addition & 1 deletion test/jmespath/input/compliance/functions.json
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@
},
{
"expression": "not_null()",
"error": "invalid-arity"
"result": null
},
{
"comment": "function projection on single arg function",
Expand Down
Loading