Skip to content

Commit 7f6137b

Browse files
committed
Unpack jv values into Python values directly
Instead of dumping and parsing JSON, convert JQ's "jv" structures into Python values directly by recursively walking them. The naive implementation is still twice as fast.
1 parent a24ce4c commit 7f6137b

File tree

1 file changed

+57
-14
lines changed

1 file changed

+57
-14
lines changed

jq.pyx

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ cdef extern from "jv.h":
2626
int jv_invalid_has_msg(jv)
2727
char* jv_string_value(jv)
2828
jv jv_dump_string(jv, int flags)
29+
int jv_is_integer(jv)
30+
double jv_number_value(jv)
31+
int jv_array_length(jv)
32+
jv jv_array_get(jv, int)
33+
int jv_object_iter(jv)
34+
int jv_object_iter_next(jv, int)
35+
int jv_object_iter_valid(jv, int)
36+
jv jv_object_iter_key(jv, int)
37+
jv jv_object_iter_value(jv, int)
2938

3039
cdef struct jv_parser:
3140
pass
@@ -51,6 +60,52 @@ cdef extern from "jq.h":
5160
void jq_get_error_cb(jq_state *, jq_err_cb *, void **)
5261

5362

63+
cdef object _jv_to_python(jv value):
64+
"""Unpack a jv value into a Python value"""
65+
cdef jv_kind kind = jv_get_kind(value)
66+
cdef int idx
67+
cdef jv property_key
68+
cdef jv property_value
69+
cdef object python_value
70+
71+
if kind == JV_KIND_INVALID:
72+
raise ValueError("Invalid value")
73+
elif kind == JV_KIND_NULL:
74+
python_value = None
75+
elif kind == JV_KIND_FALSE:
76+
python_value = False
77+
elif kind == JV_KIND_TRUE:
78+
python_value = True
79+
elif kind == JV_KIND_NUMBER:
80+
if jv_is_integer(value):
81+
python_value = int(jv_number_value(value))
82+
else:
83+
python_value = float(jv_number_value(value))
84+
elif kind == JV_KIND_STRING:
85+
python_value = jv_string_value(value).decode("utf-8")
86+
elif kind == JV_KIND_ARRAY:
87+
python_value = []
88+
for idx in range(0, jv_array_length(jv_copy(value))):
89+
property_value = jv_array_get(jv_copy(value), idx)
90+
python_value.append(_jv_to_python(property_value))
91+
elif kind == JV_KIND_OBJECT:
92+
python_value = {}
93+
idx = jv_object_iter(value)
94+
while jv_object_iter_valid(value, idx):
95+
property_key = jv_object_iter_key(value, idx)
96+
property_value = jv_object_iter_value(value, idx)
97+
try:
98+
python_value[jv_string_value(property_key).decode("utf-8")] = \
99+
_jv_to_python(property_value)
100+
finally:
101+
jv_free(property_key)
102+
idx = jv_object_iter_next(value, idx)
103+
else:
104+
raise ValueError("Invalid value kind: " + str(kind))
105+
jv_free(value)
106+
return python_value
107+
108+
54109
def compile(object program):
55110
cdef object program_bytes = program.encode("utf8")
56111
return _Program(program_bytes)
@@ -199,13 +254,7 @@ cdef class _ProgramWithInput(object):
199254
return _ResultIterator(self._jq_state_pool, self._bytes_input)
200255

201256
def text(self):
202-
iterator = self._make_iterator()
203-
results = []
204-
while True:
205-
try:
206-
results.append(iterator._next_string())
207-
except StopIteration:
208-
return "\n".join(results)
257+
return "\n".join(json.dumps(v) for v in self)
209258

210259
def all(self):
211260
return list(self)
@@ -239,9 +288,6 @@ cdef class _ResultIterator(object):
239288
return self
240289

241290
def __next__(self):
242-
return json.loads(self._next_string())
243-
244-
cdef unicode _next_string(self):
245291
cdef int dumpopts = 0
246292
while True:
247293
if not self._ready:
@@ -250,10 +296,7 @@ cdef class _ResultIterator(object):
250296

251297
result = jq_next(self._jq)
252298
if jv_is_valid(result):
253-
dumped = jv_dump_string(result, dumpopts)
254-
value = jv_string_value(dumped).decode("utf8")
255-
jv_free(dumped)
256-
return value
299+
return _jv_to_python(result)
257300
elif jv_invalid_has_msg(jv_copy(result)):
258301
error_message = jv_invalid_get_msg(result)
259302
message = jv_string_value(error_message).decode("utf8")

0 commit comments

Comments
 (0)