Skip to content

Commit a2739d7

Browse files
committedMar 18, 2025
Rework iteration to only retrieve the key, enable getting the value of the current key.
Use common active_key, fix finish(), etc. Rename example and use the key iteration. Additional tests.
1 parent f2ed1a1 commit a2739d7

File tree

3 files changed

+100
-29
lines changed

3 files changed

+100
-29
lines changed
 

‎adafruit_json_stream.py

+34-21
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def __init__(self, stream):
154154
self.finish_char = ""
155155

156156
def finish(self):
157-
"""Consume all of the characters for this list from the stream."""
157+
"""Consume all of the characters for this container from the stream."""
158158
if not self.done:
159159
if self.active_child:
160160
self.active_child.finish()
@@ -163,7 +163,8 @@ def finish(self):
163163
self.done = True
164164

165165
def as_object(self):
166-
"""Consume all of the characters for this list from the stream and return as an object."""
166+
"""Consume all of the characters for this container from the stream
167+
and return as an object."""
167168
if self.has_read:
168169
raise BufferError("Object has already been partly read.")
169170

@@ -207,10 +208,17 @@ class TransientObject(Transient):
207208
def __init__(self, stream):
208209
super().__init__(stream)
209210
self.finish_char = "}"
210-
self.active_child_key = None
211+
self.active_key = None
212+
213+
def finish(self):
214+
"""Consume all of the characters for this container from the stream."""
215+
if self.active_key and not self.active_child:
216+
self.done = self.data.fast_forward(",")
217+
self.active_key = None
218+
super().finish()
211219

212220
def __getitem__(self, key):
213-
if self.active_child and self.active_child_key == key:
221+
if self.active_child and self.active_key == key:
214222
return self.active_child
215223

216224
self.has_read = True
@@ -219,12 +227,16 @@ def __getitem__(self, key):
219227
self.active_child.finish()
220228
self.done = self.data.fast_forward(",")
221229
self.active_child = None
222-
self.active_child_key = None
230+
self.active_key = None
223231
if self.done:
224232
raise KeyError(key)
225233

226234
while not self.done:
227-
current_key = self.data.next_value(":")
235+
if self.active_key:
236+
current_key = self.active_key
237+
self.active_key = None
238+
else:
239+
current_key = self.data.next_value(":")
228240
if current_key is None:
229241
self.done = True
230242
break
@@ -234,43 +246,44 @@ def __getitem__(self, key):
234246
self.done = True
235247
if isinstance(next_value, Transient):
236248
self.active_child = next_value
237-
self.active_child_key = key
249+
self.active_key = key
238250
return next_value
239251
self.done = self.data.fast_forward(",")
240252
raise KeyError(key)
241253

242254
def __iter__(self):
243255
return self
244256

245-
def _next_item(self):
246-
"""Return the next item as a (key, value) pair, regardless of key."""
247-
if self.active_child:
248-
self.active_child.finish()
257+
def _next_key(self):
258+
"""Return the next item's key, without consuming the value."""
259+
if self.active_key:
260+
if self.active_child:
261+
self.active_child.finish()
262+
self.active_child = None
249263
self.done = self.data.fast_forward(",")
250-
self.active_child = None
264+
self.active_key = None
251265
if self.done:
252266
raise StopIteration()
253267

268+
self.has_read = True
269+
254270
current_key = self.data.next_value(":")
255271
if current_key is None:
256272
self.done = True
257273
raise StopIteration()
258274

259-
next_value = self.data.next_value(",")
260-
if self.data.last_char == ord("}"):
261-
self.done = True
262-
if isinstance(next_value, Transient):
263-
self.active_child = next_value
264-
return (current_key, next_value)
275+
self.active_key = current_key
276+
return current_key
265277

266278
def __next__(self):
267-
return self._next_item()[0]
279+
return self._next_key()
268280

269281
def items(self):
270-
"""Return iterator ine the dictionary’s items ((key, value) pairs)."""
282+
"""Return iterator in the dictionary’s items ((key, value) pairs)."""
271283
try:
272284
while not self.done:
273-
yield self._next_item()
285+
key = self._next_key()
286+
yield (key, self[key])
274287
except StopIteration:
275288
return
276289

‎examples/json_stream_advanced.py renamed to ‎examples/json_stream_local_file_advanced.py

+29-8
Original file line numberDiff line numberDiff line change
@@ -23,31 +23,52 @@ def iter_content(self, chunk_size):
2323
obj = json_stream.load(FakeResponse(f).iter_content(32))
2424

2525

26-
def find_keys(obj, keys):
26+
def find_keys(haystack, keys):
2727
"""If we don't know the order in which the keys are,
2828
go through all of them and pick the ones we want"""
29-
out = dict()
29+
out = {}
3030
# iterate on the items of an object
31-
for key, value in obj.items():
31+
for key in haystack:
3232
if key in keys:
33+
# retrieve the value only if needed
34+
value = haystack[key]
3335
# if it's a sub object, get it all
34-
if isinstance(value, json_stream.Transient):
36+
if hasattr(value, "as_object"):
3537
value = value.as_object()
3638
out[key] = value
3739
return out
3840

41+
42+
months = [
43+
"January",
44+
"February",
45+
"March",
46+
"April",
47+
"May",
48+
"June",
49+
"July",
50+
"August",
51+
"September",
52+
"October",
53+
"November",
54+
"December",
55+
]
56+
57+
3958
def time_to_date(stamp):
4059
tt = time.localtime(stamp)
41-
month = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"][tt.tm_mon]
60+
month = months[tt.tm_mon]
4261
return f"{tt.tm_mday:2d}th of {month}"
4362

63+
4464
def ftoc(temp):
4565
return (temp - 32) * 5 / 9
4666

67+
4768
currently = obj["currently"]
4869
print("Currently:")
49-
print(" ", time_to_date(currently["time"]))
50-
print(" ", currently["icon"])
70+
print(" ", time_to_date(currently["time"]))
71+
print(" ", currently["icon"])
5172

5273
# iterate on the content of a list
5374
for i, day in enumerate(obj["daily"]["data"]):
@@ -56,7 +77,7 @@ def ftoc(temp):
5677
print(
5778
f'On {date}: {day_items["summary"]},',
5879
f'Max: {int(day_items["temperatureHigh"])}F',
59-
f'({int(ftoc(day_items["temperatureHigh"]))}C)'
80+
f'({int(ftoc(day_items["temperatureHigh"]))}C)',
6081
)
6182

6283
if i > 4:

‎tests/test_json_stream.py

+37
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,18 @@ def test_iterating_keys(dict_with_keys):
696696
assert output == ["field_1", "field_2", "field_3"]
697697

698698

699+
def test_iterating_keys_get(dict_with_keys):
700+
"""Iterate through keys of a simple object and get values."""
701+
702+
the_dict = json.loads(dict_with_keys)
703+
704+
bytes_io_chunk = BytesChunkIO(dict_with_keys.encode())
705+
stream = adafruit_json_stream.load(bytes_io_chunk)
706+
for key in stream:
707+
value = stream[key]
708+
assert value == the_dict[key]
709+
710+
699711
def test_iterating_items(dict_with_keys):
700712
"""Iterate through items of a simple object."""
701713

@@ -723,3 +735,28 @@ def test_iterating_items_after_get(dict_with_keys):
723735
assert stream["field_1"] == 1
724736
output = list(stream.items())
725737
assert output == [("field_2", 2), ("field_3", 3)]
738+
739+
740+
def test_iterating_complex_dict(complex_dict):
741+
"""Mix iterating over items of objects in objects in arrays."""
742+
743+
names = ["one", "two", "three", "four"]
744+
sub_values = [None, "two point one", "three point one", None]
745+
746+
stream = adafruit_json_stream.load(BytesChunkIO(complex_dict.encode()))
747+
748+
thing_num = 0
749+
for (index, item) in enumerate(stream.items()):
750+
key, a_list = item
751+
assert key == f"list_{index+1}"
752+
for thing in a_list:
753+
assert thing["dict_name"] == names[thing_num]
754+
for sub_key in thing["sub_dict"]:
755+
# break after getting a key with or without the value
756+
# (testing finish() called from the parent list)
757+
if sub_key == "sub_dict_name":
758+
if thing_num in {1, 2}:
759+
value = thing["sub_dict"][sub_key]
760+
assert value == sub_values[thing_num]
761+
break
762+
thing_num += 1

0 commit comments

Comments
 (0)