Skip to content

Commit 18843b5

Browse files
committed
Fix iter issues
1 parent e4c0960 commit 18843b5

File tree

2 files changed

+548
-30
lines changed

2 files changed

+548
-30
lines changed

adafruit_json_stream.py

+53-30
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def __init__(self, data_iter):
2626
self.data_iter = data_iter
2727
self.i = 0
2828
self.chunk = b""
29+
self.last_char = None
2930

3031
def read(self):
3132
"""Read the next character from the stream."""
@@ -39,16 +40,23 @@ def read(self):
3940
self.i += 1
4041
return char
4142

42-
def fast_forward(self, closer):
43+
def fast_forward(self, closer, buffer=None):
4344
"""Read through the stream until the character is ``closer``, ``]``
4445
(ending a list) or ``}`` (ending an object.) Intermediate lists and
4546
objects are skipped."""
47+
4648
closer = ord(closer)
4749
close_stack = [closer]
4850
count = 0
4951
while close_stack:
5052
char = self.read()
5153
count += 1
54+
if buffer:
55+
if count == len(buffer):
56+
new_buffer = bytearray(len(buffer) + 32)
57+
new_buffer[: len(buffer)] = buffer
58+
buffer = new_buffer
59+
buffer[count] = char
5260
if char == close_stack[-1]:
5361
close_stack.pop()
5462
elif char == ord('"'):
@@ -63,6 +71,9 @@ def fast_forward(self, closer):
6371
close_stack.append(ord("}"))
6472
elif char == ord("["):
6573
close_stack.append(ord("]"))
74+
if buffer:
75+
value_string = bytes(memoryview(buffer)[: count + 1]).decode("utf-8")
76+
return json.loads(value_string)
6677
return False
6778

6879
def next_value(self, endswith=None):
@@ -77,10 +88,10 @@ def next_value(self, endswith=None):
7788
except EOFError:
7889
char = endswith
7990
if not in_string and (char == endswith or char in (ord("]"), ord("}"))):
91+
self.last_char = char
8092
if len(buf) == 0:
8193
return None
8294
value_string = bytes(buf).decode("utf-8")
83-
# print(f"{repr(value_string)}, {endswith=}")
8495
return json.loads(value_string)
8596
if char == ord("{"):
8697
return TransientObject(self)
@@ -94,40 +105,60 @@ def next_value(self, endswith=None):
94105
buf.append(char)
95106

96107

97-
class Transient: # pylint: disable=too-few-public-methods
108+
class Transient:
98109
"""Transient object representing a JSON object."""
99110

100-
# This is helpful for checking that something is a TransientList or TransientObject.
101-
102-
103-
class TransientList(Transient):
104-
"""Transient object that acts like a list through the stream."""
105-
106111
def __init__(self, stream):
112+
self.active_child = None
107113
self.data = stream
108114
self.done = False
109-
self.active_child = None
115+
self.has_read = False
116+
self.finish_char = ""
117+
self.start_char = ""
110118

111119
def finish(self):
112120
"""Consume all of the characters for this list from the stream."""
113121
if not self.done:
114122
if self.active_child:
115123
self.active_child.finish()
116124
self.active_child = None
117-
self.data.fast_forward("]")
125+
self.data.fast_forward(self.finish_char)
118126
self.done = True
119127

128+
def as_object(self):
129+
"""Consume all of the characters for this list from the stream and return as an object."""
130+
if self.has_read:
131+
raise BufferError("Object has already been partly read.")
132+
133+
buffer = bytearray(32)
134+
buffer[0] = ord(self.start_char)
135+
self.done = True
136+
return self.data.fast_forward(self.finish_char, buffer)
137+
138+
139+
class TransientList(Transient):
140+
"""Transient object that acts like a list through the stream."""
141+
142+
def __init__(self, stream):
143+
super().__init__(stream)
144+
self.finish_char = "]"
145+
self.start_char = "["
146+
120147
def __iter__(self):
121148
return self
122149

123150
def __next__(self):
151+
self.has_read = True
152+
124153
if self.active_child:
125154
self.active_child.finish()
126155
self.done = self.data.fast_forward(",")
127156
self.active_child = None
128157
if self.done:
129158
raise StopIteration()
130159
next_value = self.data.next_value(",")
160+
if self.data.last_char == ord("]"):
161+
self.done = True
131162
if next_value is None:
132163
self.done = True
133164
raise StopIteration()
@@ -140,42 +171,34 @@ class TransientObject(Transient):
140171
"""Transient object that acts like a dictionary through the stream."""
141172

142173
def __init__(self, stream):
143-
self.data = stream
144-
self.done = False
145-
self.buf = array.array("B")
146-
147-
self.active_child = None
148-
149-
def finish(self):
150-
"""Consume all of the characters for this object from the stream."""
151-
if not self.done:
152-
if self.active_child:
153-
self.active_child.finish()
154-
self.active_child = None
155-
self.data.fast_forward("}")
156-
self.done = True
174+
super().__init__(stream)
175+
self.finish_char = "}"
176+
self.start_char = "{"
157177

158178
def __getitem__(self, key):
179+
self.has_read = True
180+
159181
if self.active_child:
160182
self.active_child.finish()
161183
self.done = self.data.fast_forward(",")
162184
self.active_child = None
163185
if self.done:
164-
raise KeyError()
186+
raise KeyError(key)
165187

166-
while True:
188+
while not self.done:
167189
current_key = self.data.next_value(":")
168190
if current_key is None:
169-
# print("object done", self)
170191
self.done = True
171192
break
172193
if current_key == key:
173194
next_value = self.data.next_value(",")
195+
if self.data.last_char in [ord("}"), ord("]")]:
196+
self.done = True
174197
if isinstance(next_value, Transient):
175198
self.active_child = next_value
176199
return next_value
177-
self.data.fast_forward(",")
178-
raise KeyError()
200+
self.done = self.data.fast_forward(",")
201+
raise KeyError(key)
179202

180203

181204
def load(data_iter):

0 commit comments

Comments
 (0)