From ceb45a7f5e68a516ac4e29a36670027472dd8f89 Mon Sep 17 00:00:00 2001 From: TANG ZHIXIONG Date: Wed, 14 May 2025 23:32:23 +0800 Subject: [PATCH 1/4] fix --- src/main.cpp | 21 +++++++++++++++++++++ src/pybind11_jsoncons/__init__.py | 2 ++ tests/test_basic.py | 9 +++++++++ 3 files changed, 32 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index ea583eb..6ec5d06 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ using json = jsoncons::ojson; // using json = jsoncons::json; namespace jmespath = jsoncons::jmespath; namespace msgpack = jsoncons::msgpack; +using jmespath_expr_type = jmespath::jmespath_expression; namespace py = pybind11; using rvp = py::return_value_policy; @@ -661,6 +662,26 @@ PYBIND11_MODULE(_core, m) { str: JSON string representation )pbdoc"); + py::class_(m, "JMESPathExpr", py::module_local(), py::dynamic_attr()) // + .def("evaluate", [](const jmespath_expr_type &self, const json &doc) -> json { + return self.evaluate(doc); + }, "doc"_a, R"pbdoc( + Evaluate the JMESPath expression against a JSON document. + + Args: + doc: JSON document + + Returns: + json: Result of the evaluation + )pbdoc") + // + .static_def("build", [](const std::string &expr_text) -> jmespath_expr_type { + return jmespath::make_expression(expr_text); + }, "expr_text"_a, R"pbdoc( + Create a new JMESPath expression. + )pbdoc") + ; + // m.def("dumps", [](const json &json_val) -> std::string { // return json_val.to_string(); // }); diff --git a/src/pybind11_jsoncons/__init__.py b/src/pybind11_jsoncons/__init__.py index 4de47e5..806a4bb 100644 --- a/src/pybind11_jsoncons/__init__.py +++ b/src/pybind11_jsoncons/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations from ._core import ( + JMESPathExpr, Json, JsonQuery, JsonQueryRepl, @@ -15,6 +16,7 @@ "__version__", "JsonQuery", "JsonQueryRepl", + "JMESPathExpr", "Json", "msgpack_decode", "msgpack_encode", diff --git a/tests/test_basic.py b/tests/test_basic.py index 86e6ee8..35d22f4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -95,6 +95,13 @@ def test_json_query(): assert json.loads(repl.eval("name")) == "Baby" assert not json.loads(repl.eval("age >= `18`")) + assert repl.doc.to_json() == '{"age":5,"other":"too young","name":"Baby"}' + repl.doc.from_python(people[1]) + assert repl.doc.to_json() == '{"age":20,"other":"foo","name":"Bob"}' + repl.debug = False + assert not repl.debug + assert repl.eval("age == `20`") == "true" + jql = m.JsonQuery() with pytest.raises(RuntimeError) as excinfo: jql.setup_predicate("[*].[") @@ -151,3 +158,5 @@ def test_json_query_json(): # pytest -vs tests/test_basic.py + +test_json_query() From 590833fd5d5ae6ab980395cb93f97ce4a32027c0 Mon Sep 17 00:00:00 2001 From: TANG ZHIXIONG Date: Wed, 14 May 2025 23:42:14 +0800 Subject: [PATCH 2/4] fix --- pyproject.toml | 4 ++-- src/main.cpp | 25 +++++++++++++++++++++++-- tests/test_basic.py | 9 ++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 37a3802..bd4a006 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "scikit_build_core.build" [project] name = "pybind11_jsoncons" -version = "0.1.1" -description="python binding for jsoncons (only what I needed for now)" +version = "0.1.2" +description="python binding for jsoncons + msgpack + jmespath (only what I needed for now)" readme = "README.md" authors = [ { name = "zhixiong.tang", email = "dvorak4tzx@email.com" }, diff --git a/src/main.cpp b/src/main.cpp index 6ec5d06..06498f8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -226,6 +226,19 @@ struct JsonQueryRepl { return result.to_string(); } + /** + * Evaluate a JMESPath expression against the JSON document. + * @param expr JMESPath expression + * @return Result of the evaluation as a json object + */ + json eval_expr(const jmespath_expr_type &expr) const { + auto result = expr.evaluate(doc, params_); + if (debug) { + std::cerr << pretty_print(result) << std::endl; + } + return result; + } + /** * Add parameters for JMESPath evaluation. * @param key Parameter key @@ -395,7 +408,6 @@ struct JsonQuery { std::vector>> transforms_expr_; std::map params_; - std::deque> outputs_; bool __matches(const json &msg) const { @@ -534,6 +546,15 @@ PYBIND11_MODULE(_core, m) { Returns: str: Result of the evaluation as a string )pbdoc") + .def("eval_expr", &JsonQueryRepl::eval_expr, "expr"_a, R"pbdoc( + Evaluate a JMESPath expression against the JSON document. + + Args: + expr: JMESPath expression + + Returns: + json: Result of the evaluation as a json object + )pbdoc") .def("add_params", &JsonQueryRepl::add_params, "key"_a, "value"_a, R"pbdoc( Add parameters for JMESPath evaluation. @@ -675,7 +696,7 @@ PYBIND11_MODULE(_core, m) { json: Result of the evaluation )pbdoc") // - .static_def("build", [](const std::string &expr_text) -> jmespath_expr_type { + .def_static("build", [](const std::string &expr_text) -> jmespath_expr_type { return jmespath::make_expression(expr_text); }, "expr_text"_a, R"pbdoc( Create a new JMESPath expression. diff --git a/tests/test_basic.py b/tests/test_basic.py index 35d22f4..ca1ec91 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -8,7 +8,7 @@ def test_version(): - assert m.__version__ == "0.1.1" + assert m.__version__ == "0.1.2" def test_repl(): @@ -102,6 +102,11 @@ def test_json_query(): assert not repl.debug assert repl.eval("age == `20`") == "true" + expr = m.JMESPathExpr.build("age == `20`") + assert isinstance(expr, m.JMESPathExpr) + assert expr.evaluate(m.Json().from_python(people[1])).to_python() + assert repl.eval_expr(expr).to_python() + jql = m.JsonQuery() with pytest.raises(RuntimeError) as excinfo: jql.setup_predicate("[*].[") @@ -158,5 +163,3 @@ def test_json_query_json(): # pytest -vs tests/test_basic.py - -test_json_query() From afe32242b666600e9c668a3c17e8f9a9da860fee Mon Sep 17 00:00:00 2001 From: TANG ZHIXIONG Date: Wed, 14 May 2025 23:44:07 +0800 Subject: [PATCH 3/4] add docstring --- src/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 06498f8..886e19d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -410,6 +410,11 @@ struct JsonQuery { std::deque> outputs_; + /** + * Internal method to check if a JSON document matches the predicate. + * @param msg JSON document to check + * @return True if the document matches the predicate, false otherwise + */ bool __matches(const json &msg) const { auto ret = predicate_expr_->evaluate(msg, params_); return /*ret.is_bool() && */ ret.as_bool(); From dbda1bb4fcac4fce637d986a86be723c682c27eb Mon Sep 17 00:00:00 2001 From: TANG ZHIXIONG Date: Wed, 14 May 2025 23:46:03 +0800 Subject: [PATCH 4/4] update stub --- src/pybind11_jsoncons/__init__.pyi | 62 +++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/pybind11_jsoncons/__init__.pyi b/src/pybind11_jsoncons/__init__.pyi index 532a85e..f59f073 100644 --- a/src/pybind11_jsoncons/__init__.pyi +++ b/src/pybind11_jsoncons/__init__.pyi @@ -72,17 +72,38 @@ class Json: """ Convert a Python object to a JSON object. + This method converts various Python types to their JSON equivalents: + - None -> null + - bool -> boolean + - int -> integer + - float -> number + - str -> string + - list/tuple -> array + - dict -> object + Args: obj: Python object to convert Returns: - Json: Reference to self + Json: Reference to self with converted data + + Raises: + RuntimeError: If the Python object contains circular references or unsupported types """ def to_python(self) -> Any: """ Convert a JSON object to a Python object. + This method converts JSON types to their Python equivalents: + - null -> None + - boolean -> bool + - integer -> int + - number -> float + - string -> str + - array -> list + - object -> dict + Returns: Any: Python object representation of the JSON data """ @@ -122,6 +143,17 @@ class JsonQueryRepl: str: Result of the evaluation as a string """ + def eval_expr(self, expr: JMESPathExpr) -> Json: + """ + Evaluate a JMESPath expression against the JSON document. + + Args: + expr: JMESPath expression + + Returns: + Json: Result of the evaluation as a json object + """ + def add_params(self, key: str, value: str) -> None: """ Add parameters for JMESPath evaluation. @@ -241,6 +273,34 @@ class JsonQuery: Clear all processed data. """ +class JMESPathExpr: + """ + A class representing a compiled JMESPath expression. + """ + + def evaluate(self, doc: Json) -> Json: + """ + Evaluate the JMESPath expression against a JSON document. + + Args: + doc: JSON document + + Returns: + Json: Result of the evaluation + """ + + @staticmethod + def build(expr_text: str) -> JMESPathExpr: + """ + Create a new JMESPath expression. + + Args: + expr_text: JMESPath expression text + + Returns: + JMESPathExpr: Compiled JMESPath expression + """ + def msgpack_decode(msgpack_bytes: bytes) -> str: """ Convert MessagePack binary data to a JSON string.