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 ea583eb..886e19d 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; @@ -225,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 @@ -394,9 +408,13 @@ struct JsonQuery { std::vector>> transforms_expr_; std::map params_; - 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(); @@ -533,6 +551,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. @@ -661,6 +688,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") + // + .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. + )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/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. diff --git a/tests/test_basic.py b/tests/test_basic.py index 86e6ee8..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(): @@ -95,6 +95,18 @@ 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" + + 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("[*].[")