diff --git a/README.md b/README.md index 7332c55..be599dd 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ print(o.find_first(d)) # Output: "value" #### Avaible methodes: - `CsonPath(path)`: create a new csonpath object. +- `CsonPath(path, return_empty_array)`: Create a new csonpath object and return an empty list instead of None (like jsonpath-ng). - `OBJ.find_first(self, json)`: take a json, return first found occurrence. - `OBJ.find_all(self, json)`: take a json, return all found occurrence in a list. - `OBJ.remove(self, json)`: take a json, remove all found occurrence from it. diff --git a/csonpath.h b/csonpath.h index 6348c97..063b58a 100644 --- a/csonpath.h +++ b/csonpath.h @@ -111,6 +111,7 @@ struct csonpath { int regex_cnt; regex_t *regexs; #endif + int return_empty_array; }; struct csonpath_child_info { @@ -204,6 +205,9 @@ static inline int csonpath_init(struct csonpath cjp[static 1], if (!cjp->path || !cjp->inst_lst) { return -ENOMEM; } + + cjp->return_empty_array = 0; + return 0; } @@ -845,7 +849,7 @@ need_reloop_in = 0; #define CSONPATH_DO_FILTER_FIND CSONPATH_DO_FIND_ALL #define CSONPATH_DO_FIND_ALL_OUT \ - if (!nb_res) { \ + if (!cjp->return_empty_array && !nb_res) { \ return CSONPATH_NONE_FOUND_RET; \ } \ return ret_ar; @@ -853,7 +857,6 @@ need_reloop_in = 0; #define CSONPATH_DO_EXTRA_ARGS_IN , ret_ar #define CSONPATH_DO_EXTRA_DECLATION , CSONPATH_FIND_ALL_RET ret_ar - #include "csonpath_do.h" /* Delete */ diff --git a/csonpath_do.h b/csonpath_do.h index 614bc92..3dd916d 100644 --- a/csonpath_do.h +++ b/csonpath_do.h @@ -382,7 +382,7 @@ static CSONPATH_DO_RET_TYPE csonpath_do_(struct csonpath cjp[static 1], CSONPATH_DO_POST_OPERATION; - return ret; + return ret; } #ifndef CSONPATH_NO_UNDEF diff --git a/csonpath_python.c b/csonpath_python.c index 067f8e1..701693f 100644 --- a/csonpath_python.c +++ b/csonpath_python.c @@ -1,4 +1,5 @@ #include +#include #define CSONPATH_JSON PyObject * @@ -207,33 +208,53 @@ typedef struct { static PyObject *PyCsonPath_new(PyTypeObject *subtype, PyObject* args, - PyObject* dont_care) + PyObject* kwargs) { - PyCsonPathObject *self = (PyCsonPathObject *)subtype->tp_alloc(subtype, 0); - const char *s; - - if (!self) - BAD_ARG(); - - if (!PyArg_ParseTuple(args, "s", &s)) - BAD_ARG(); - - struct csonpath *ret = malloc(sizeof *ret); - if (!self) - return PyErr_NoMemory(); - - if (csonpath_init(ret, s) < 0) - return PyErr_NoMemory(); - - if (csonpath_compile(ret) < 0) { - char *error = ret->compile_error; - PyErr_Format(PyExc_ValueError, "compilation fail %s", error ? error : "(unknow error)"); - csonpath_destroy(ret); - return NULL; - } - self->cp = ret; - - return (PyObject *)self; + static char *kwlist[] = {"path", "return_empty_array", NULL}; + PyCsonPathObject *self = (PyCsonPathObject *)subtype->tp_alloc(subtype, 0); + const char *s; + int return_empty_array = 0; + struct csonpath *ret = NULL; + PyObject *py_ret = NULL; + + if (!self) + BAD_ARG(); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|p", kwlist, + &s, &return_empty_array)) { + goto error; + } + + ret = malloc(sizeof *ret); + if (!ret) { + PyErr_NoMemory(); + goto error; + } + + if (csonpath_init(ret, s) < 0) { + py_ret = PyErr_NoMemory(); + goto error_free_ret; + } + + ret->return_empty_array = return_empty_array; + + if (csonpath_compile(ret) < 0) { + char *err_str = ret->compile_error; + PyErr_Format(PyExc_ValueError, "compilation fail %s", + err_str ? err_str : "(unknow error)"); + goto error_destroy_ret; + } + + self->cp = ret; + return (PyObject *)self; + + error_destroy_ret: + csonpath_destroy(ret); + error_free_ret: + free(ret); + error: + Py_DECREF(self); + return py_ret; } static PyObject *find_all(PyCsonPathObject *self, PyObject* args) @@ -241,8 +262,10 @@ static PyObject *find_all(PyCsonPathObject *self, PyObject* args) PyObject *json; if (!PyArg_ParseTuple(args, "O", &json)) - BAD_ARG(); + BAD_ARG(); + PyObject *ret = csonpath_find_all(self->cp, json); + return ret; } @@ -251,8 +274,10 @@ static PyObject *find_first(PyCsonPathObject *self, PyObject* args) PyObject *json; if (!PyArg_ParseTuple(args, "O", &json)) - BAD_ARG(); + BAD_ARG(); + PyObject *ret = csonpath_find_first(self->cp, json); + Py_INCREF(ret); return ret; } diff --git a/tests/json-c/filter.c b/tests/json-c/filter.c index 276d129..20efb74 100644 --- a/tests/json-c/filter.c +++ b/tests/json-c/filter.c @@ -60,6 +60,16 @@ int main(void) ret = csonpath_find_first(&p, jobj); assert(ret); + assert(csonpath_set_path(&p, "$.array[?a==9999]") == 0); + p.return_empty_array = 0; + ret = csonpath_find_first(&p, jobj); + assert(ret == NULL); + + p.return_empty_array = 1; + ret = csonpath_find_first(&p, jobj); + assert(ret == NULL); + p.return_empty_array = 0; + csonpath_set_path(&p, "$.array[?(@[\"a\"]==\"la\")]"); ret = csonpath_find_first(&p, jobj); assert(ret); @@ -138,6 +148,18 @@ int main(void) assert(!csonpath_set_path(&p, "$.ha[?(@[\"i\"][\"h\"] =~ \"gan\")][\"i\"][\"h\"]")); assert(csonpath_find_first(&p, jobj)); + assert(!csonpath_set_path(&p, "$.ha[?i.h=~\"no_match_xyz\"]")); + p.return_empty_array = 1; + ret = csonpath_find_all(&p, jobj); + assert(ret != NULL); + assert(json_object_is_type(ret, json_type_array)); + assert(json_object_array_length(ret) == 0); + json_object_put(ret); + p.return_empty_array = 0; + ret = csonpath_find_all(&p, jobj); + assert(ret == NULL); + json_object_put(ret); + json_object_put(jobj); csonpath_destroy(&p); diff --git a/tests/python/test_filter.py b/tests/python/test_filter.py index 026dea7..b038495 100644 --- a/tests/python/test_filter.py +++ b/tests/python/test_filter.py @@ -49,6 +49,23 @@ def test_simple_filter(): assert ret == ["Leodagan"], dict + dict = {"ha": [{"h": {"h1": "Leodagan"}}, {"h": "George"}]} + cp = csonpath.CsonPath('$.ha[?h.h1 =~ "Assim"].h.h1') + ret = cp.find_all(dict) + + assert ret is None + + dict = {"ha": [{"h": {"h1": "Leodagan"}}, {"h": "George"}]} + cp = csonpath.CsonPath('$.ha[?h.h1 =~ "Assim"].h.h1', return_empty_array=True) + ret = cp.find_all(dict) + + assert ret == [] + + cp = csonpath.CsonPath('$.ha[?h.h1 =~ "Assim"].h.h1', True) + ret = cp.find_all(dict) + + assert ret == [] + def test_multiple_filters(): # cf. https://github.com/h2non/jsonpath-ng?tab=readme-ov-file#extensions, last example of filter @@ -66,3 +83,13 @@ def test_multiple_filters(): cp = csonpath.CsonPath('$.knights[?laterality = "left" & sub.a = 10].sub') ret = cp.find_first(dict) assert ret == {"a": 10} + + cp = csonpath.CsonPath('$.knights[?(@.name == "Assim")].laterality') + ret = cp.find_first(dict) + + assert ret is None + + cp = csonpath.CsonPath('$.knights[?(@.name == "Assim")].laterality', return_empty_array=True) + ret = cp.find_first(dict) + + assert ret is None diff --git a/tests/python/test_needrefacto.py b/tests/python/test_needrefacto.py index db949aa..0c42a7c 100644 --- a/tests/python/test_needrefacto.py +++ b/tests/python/test_needrefacto.py @@ -20,6 +20,11 @@ def test_torefacto(): assert r is None + o = csonpath.CsonPath("$.C", True) + r = o.find_first(d) + + assert r is None + o = csonpath.CsonPath("$[*]") r = o.find_all(d)