Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/danielaparker/jsoncons in…
Browse files Browse the repository at this point in the history
…to csv_reader
  • Loading branch information
danielaparker committed Nov 26, 2024
2 parents 66e4144 + 04a3ceb commit 2713f61
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 3 deletions.
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

0 comments on commit 2713f61

Please sign in to comment.