diff options
| author | Michael Williamson <mike@zwobble.org> | 2020-09-13 16:18:20 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-13 16:18:20 +0100 |
| commit | 006b54f8f8698fd79bc634535664a85aaafc5280 (patch) | |
| tree | 58a58aef6274967c1b9e0649919b649be10f7180 | |
| parent | 08dfaba73d51636f194eed716cfe4b5dde2b57ff (diff) | |
| parent | 7f6137b8602393ebbe6c23b00a482b2917495d3f (diff) | |
Merge pull request #50 from spbnick/unpack_jv_values_directly
Unpack jv values directly
| -rw-r--r-- | jq.pyx | 73 | ||||
| -rw-r--r-- | tests/jq_tests.py | 103 |
2 files changed, 161 insertions, 15 deletions
@@ -26,6 +26,15 @@ cdef extern from "jv.h": int jv_invalid_has_msg(jv) char* jv_string_value(jv) jv jv_dump_string(jv, int flags) + int jv_is_integer(jv) + double jv_number_value(jv) + int jv_array_length(jv) + jv jv_array_get(jv, int) + int jv_object_iter(jv) + int jv_object_iter_next(jv, int) + int jv_object_iter_valid(jv, int) + jv jv_object_iter_key(jv, int) + jv jv_object_iter_value(jv, int) cdef struct jv_parser: pass @@ -51,6 +60,52 @@ cdef extern from "jq.h": void jq_get_error_cb(jq_state *, jq_err_cb *, void **) +cdef object _jv_to_python(jv value): + """Unpack a jv value into a Python value""" + cdef jv_kind kind = jv_get_kind(value) + cdef int idx + cdef jv property_key + cdef jv property_value + cdef object python_value + + if kind == JV_KIND_INVALID: + raise ValueError("Invalid value") + elif kind == JV_KIND_NULL: + python_value = None + elif kind == JV_KIND_FALSE: + python_value = False + elif kind == JV_KIND_TRUE: + python_value = True + elif kind == JV_KIND_NUMBER: + if jv_is_integer(value): + python_value = int(jv_number_value(value)) + else: + python_value = float(jv_number_value(value)) + elif kind == JV_KIND_STRING: + python_value = jv_string_value(value).decode("utf-8") + elif kind == JV_KIND_ARRAY: + python_value = [] + for idx in range(0, jv_array_length(jv_copy(value))): + property_value = jv_array_get(jv_copy(value), idx) + python_value.append(_jv_to_python(property_value)) + elif kind == JV_KIND_OBJECT: + python_value = {} + idx = jv_object_iter(value) + while jv_object_iter_valid(value, idx): + property_key = jv_object_iter_key(value, idx) + property_value = jv_object_iter_value(value, idx) + try: + python_value[jv_string_value(property_key).decode("utf-8")] = \ + _jv_to_python(property_value) + finally: + jv_free(property_key) + idx = jv_object_iter_next(value, idx) + else: + raise ValueError("Invalid value kind: " + str(kind)) + jv_free(value) + return python_value + + def compile(object program): cdef object program_bytes = program.encode("utf8") return _Program(program_bytes) @@ -199,13 +254,7 @@ cdef class _ProgramWithInput(object): return _ResultIterator(self._jq_state_pool, self._bytes_input) def text(self): - iterator = self._make_iterator() - results = [] - while True: - try: - results.append(iterator._next_string()) - except StopIteration: - return "\n".join(results) + return "\n".join(json.dumps(v) for v in self) def all(self): return list(self) @@ -239,9 +288,6 @@ cdef class _ResultIterator(object): return self def __next__(self): - return json.loads(self._next_string()) - - cdef unicode _next_string(self): cdef int dumpopts = 0 while True: if not self._ready: @@ -250,16 +296,14 @@ cdef class _ResultIterator(object): result = jq_next(self._jq) if jv_is_valid(result): - dumped = jv_dump_string(result, dumpopts) - value = jv_string_value(dumped).decode("utf8") - jv_free(dumped) - return value + return _jv_to_python(result) elif jv_invalid_has_msg(jv_copy(result)): error_message = jv_invalid_get_msg(result) message = jv_string_value(error_message).decode("utf8") jv_free(error_message) raise ValueError(message) else: + jv_free(result) self._ready = False cdef bint _ready_next_input(self) except 1: @@ -274,6 +318,7 @@ cdef class _ResultIterator(object): jv_free(error_message) raise ValueError(u"parse error: " + message) else: + jv_free(value) raise StopIteration() diff --git a/tests/jq_tests.py b/tests/jq_tests.py index 27734e6..6b63556 100644 --- a/tests/jq_tests.py +++ b/tests/jq_tests.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals -from nose.tools import istest, assert_equal, assert_raises +from nose.tools import istest, assert_equal, assert_is, assert_raises import jq @@ -198,6 +198,107 @@ def program_string_can_be_retrieved_from_program(): @istest +def program_preserves_null(): + program = jq.compile(".") + assert_is(None, program.input(text='null').first()) + + +@istest +def program_preserves_bools(): + program = jq.compile(".") + assert_is(False, program.input(text="false").first()) + assert_is(True, program.input(text="true").first()) + + +@istest +def program_preserves_ints(): + program = jq.compile(".") + assert_equal(int, type(program.input(text="0").first())) + assert_equal(0, program.input(text="0").first()) + assert_equal(1, program.input(text="1").first()) + assert_equal(-1, program.input(text="-1").first()) + assert_equal(12345, program.input(text="12345").first()) + assert_equal(-12345, program.input(text="-12345").first()) + + +@istest +def program_preserves_floats(): + program = jq.compile(".") + assert_equal(float, type(program.input(text="1.1").first())) + assert_equal(3.14159, program.input(text="3.14159").first()) + assert_equal(-3.14159, program.input(text="-3.14159").first()) + assert_equal(42E100, program.input(text="42E100").first()) + assert_equal(-42E100, program.input(text="-42E100").first()) + + +@istest +def program_preserves_strings(): + program = jq.compile(".") + assert_equal(type(""), type(program.input(text='""').first())) + assert_equal("", program.input(text='""').first()) + assert_equal("x", program.input(text='"x"').first()) + assert_equal("xyz", program.input(text='"xyz"').first()) + + +@istest +def program_preserves_arrays(): + program = jq.compile(".") + + assert_equal(list, type(program.input(text='[]').first())) + + assert_equal([], program.input(text='[]').first()) + + assert_equal([1], program.input(text='[1]').first()) + assert_equal([1, 2], program.input(text='[1, 2]').first()) + + assert_equal([3.14159], program.input(text='[3.14159]').first()) + assert_equal([3.14159, 95141.3], program.input(text='[3.14159, 95141.3]').first()) + + assert_equal([False], program.input(text='[false]').first()) + assert_equal([False, True], program.input(text='[false, true]').first()) + + assert_equal([[]], program.input(text='[[]]').first()) + assert_equal([[[]]], program.input(text='[[[]]]').first()) + assert_equal([[], []], program.input(text='[[], []]').first()) + assert_equal([[[], []], [[], []]], program.input(text='[[[], []], [[], []]]').first()) + + assert_equal([{}], program.input(text='[{}]').first()) + assert_equal([{"": []}], program.input(text='[{"": []}]').first()) + assert_equal([{"1": [], "2": []}], program.input(text='[{"1": [], "2": []}]').first()) + + +@istest +def program_preserves_objects(): + program = jq.compile(".") + + assert_equal(dict, type(program.input(text='{}').first())) + + assert_equal({}, program.input(text='{}').first()) + + assert_equal({"": True}, program.input(text='{"": true}').first()) + assert_equal({"": False}, program.input(text='{"": false}').first()) + + assert_equal({"": 0}, program.input(text='{"": 0}').first()) + assert_equal({"": 1}, program.input(text='{"": 1}').first()) + assert_equal({"a": 0, "b": 1}, program.input(text='{"a": 0, "b": 1}').first()) + assert_equal({"": 3.14159}, program.input(text='{"": 3.14159}').first()) + assert_equal({"a": 3.14159, "b": 95141.3}, + program.input(text='{"a": 3.14159, "b": 95141.3}').first()) + + assert_equal({"": ""}, program.input(text='{"": ""}').first()) + assert_equal({"": "x"}, program.input(text='{"": "x"}').first()) + + assert_equal({"": []}, program.input(text='{"": []}').first()) + assert_equal({"": [{}]}, program.input(text='{"": [{}]}').first()) + assert_equal({"a": [1], "b": [2]}, + program.input(text='{"a": [1], "b": [2]}').first()) + + assert_equal({"": {}}, program.input(text='{"": {}}').first()) + assert_equal({"a": {}, "b": {}}, program.input(text='{"a": {}, "b": {}}').first()) + assert_equal({"": {"": {}}}, program.input(text='{"": {"": {}}}').first()) + + +@istest class ConvenienceFunctions(object): @istest def first_function_with_json_value_input_returns_first_output_element(self): |
