Skip to content

Commit bf8dafe

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 bf8dafe

File tree

1 file changed

+73
-5
lines changed

1 file changed

+73
-5
lines changed

jq.pyx

Lines changed: 73 additions & 5 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 idx_key
68+
cdef jv idx_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+
idx_value = jv_array_get(jv_copy(value), idx)
90+
python_value.append(_jv_to_python(idx_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+
idx_key = jv_object_iter_key(value, idx)
96+
idx_value = jv_object_iter_value(value, idx)
97+
try:
98+
python_value[jv_string_value(idx_key).decode("utf-8")] = \
99+
_jv_to_python(idx_value)
100+
finally:
101+
jv_free(idx_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)
@@ -239,21 +294,34 @@ cdef class _ResultIterator(object):
239294
return self
240295

241296
def __next__(self):
242-
return json.loads(self._next_string())
297+
cdef jv value
298+
self._next_jv(&value)
299+
return _jv_to_python(value)
243300

244301
cdef unicode _next_string(self):
245302
cdef int dumpopts = 0
303+
cdef jv value
304+
cdef jv dump
305+
306+
self._next_jv(&value)
307+
dump = jv_dump_string(value, dumpopts)
308+
try:
309+
string = jv_string_value(dump).decode("utf8")
310+
finally:
311+
jv_free(dump)
312+
313+
return string
314+
315+
cdef int _next_jv(self, jv *presult) except 1:
246316
while True:
247317
if not self._ready:
248318
self._ready_next_input()
249319
self._ready = True
250320

251321
result = jq_next(self._jq)
252322
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
323+
presult[0] = result
324+
return 0
257325
elif jv_invalid_has_msg(jv_copy(result)):
258326
error_message = jv_invalid_get_msg(result)
259327
message = jv_string_value(error_message).decode("utf8")

0 commit comments

Comments
 (0)