Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from string import StringReader
3class JsonError(Error):
4 message: string
6trait Value:
7 """A value in a JSON document.
9 """
11 func object(self) -> {string: Value}:
12 """As an object. Raises an error for non-object values.
14 """
16 raise NotImplementedError()
18 func get(self, key: string) -> Value:
19 """As an item in an object. Raises an error for non-object values and
20 if the key is missing.
22 """
24 raise NotImplementedError()
26 func list(self) -> [Value]:
27 """As a list. Raises an error for non-list values.
29 """
31 raise NotImplementedError()
33 func at(self, index: i64) -> Value:
34 """As an item in a list. Raises an error for non-list values.
36 """
38 raise NotImplementedError()
40 func string(self) -> string:
41 """As a string. Raises an error for non-string values.
43 """
45 raise NotImplementedError()
47 func integer(self) -> i64:
48 """As an integer. Raises an error for non-integer values.
50 """
52 raise NotImplementedError()
54 func float(self) -> f64:
55 """As a float. Raises an error for non-float values.
57 """
59 raise NotImplementedError()
61 func bool(self) -> bool:
62 """As a boolean. Raises an error for non-bool values.
64 """
66 raise NotImplementedError()
68 func null(self):
69 """As null. Raises an error for non-null values.
71 """
73 raise NotImplementedError()
75class Object(Value):
76 """An object.
78 """
80 items: {string: Value}
82 func __init__(self):
83 self.items = {}
85 func object(self) -> {string: Value}:
86 return self.items
88 func get(self, key: string) -> Value:
89 return self.items[key]
91class List(Value):
92 """A list.
94 """
96 items: [Value]
98 func __init__(self):
99 self.items = []
101 func list(self) -> [Value]:
102 return self.items
104 func at(self, index: i64) -> Value:
105 return self.items[index]
107class String(Value):
108 """A string.
110 """
112 value: string
114 func string(self) -> string:
115 return self.value
117class Integer(Value):
118 """An integer.
120 """
122 value: i64
124 func integer(self) -> i64:
125 return self.value
127class Float(Value):
128 """A float.
130 """
132 value: f64
134 func float(self) -> f64:
135 return self.value
137class Bool(Value):
138 """A boolean.
140 """
142 value: bool
144 func bool(self) -> bool:
145 return self.value
147class Null(Value):
148 """Null.
150 """
152 func null(self):
153 pass
155trait _StackValue:
157 func handle_value(self, value: Value):
158 pass
160 func handle_comma(self):
161 self.expecting_another_item = True
163 func handle_colon(self):
164 raise NotImplementedError()
166 func finalize(self, ch: char):
167 pass
169class _StackObject(_StackValue):
170 value: Object
171 key: String?
172 expecting_another_item: bool
174 func __init__(self, value: Object):
175 self.value = value
176 self.key = None
177 self.expecting_another_item = False
179 func handle_value(self, value: Value):
180 if self.key is None:
181 self.key = value
182 else:
183 self.value.items[self.key.string()] = value
184 self.key = None
185 self.expecting_another_item = False
187 func handle_colon(self):
188 if self.key is None:
189 raise NotImplementedError()
191 func finalize(self, ch: char):
192 if self.key is not None:
193 raise NotImplementedError()
195 if self.expecting_another_item:
196 raise NotImplementedError()
198 if ch != '}':
199 raise NotImplementedError()
201class _StackList(_StackValue):
202 value: List
203 expecting_another_item: bool
205 func __init__(self, value: List):
206 self.value = value
207 self.expecting_another_item = False
209 func handle_value(self, value: Value):
210 self.value.items.append(value)
211 self.expecting_another_item = False
213 func finalize(self, ch: char):
214 if self.expecting_another_item:
215 raise NotImplementedError()
217 if ch != ']':
218 raise NotImplementedError()
220func _decode_string(reader: StringReader) -> String:
221 value = ""
223 while True:
224 ch = reader.get()
226 if ch == '\"':
227 break
228 elif ch == '\\':
229 ch = reader.get()
231 if ch in "\"/\\":
232 value += ch
233 elif ch == 'b':
234 value += '\b'
235 elif ch == 'f':
236 value += '\f'
237 elif ch == 'r':
238 value += '\r'
239 elif ch == 'n':
240 value += '\n'
241 elif ch == 't':
242 value += '\t'
243 elif ch == 'u':
244 value += char(i64(reader.get()) << 24
245 | i64(reader.get()) << 16
246 | i64(reader.get()) << 8
247 | i64(reader.get()) << 0)
248 else:
249 raise JsonError(f"Unexpected character '{ch}'.")
250 elif ch == '':
251 raise JsonError("Out of data when parsing string.")
252 else:
253 value += ch
255 return String(value)
257func _decode_number(reader: StringReader, ch: char) -> Value:
258 data = ""
260 if ch == '-':
261 data += ch
262 else:
263 reader.unget()
265 data += _decode_digits(reader)
266 fraction = _decode_fraction(reader)
267 exponent = _decode_exponent(reader)
269 if fraction is None:
270 return Integer(i64(data + exponent))
271 else:
272 return Float(f64(data + fraction + exponent))
274func _decode_digits(reader: StringReader) -> string:
275 data = ""
276 ch = reader.get()
278 if ch not in "0123456789":
279 raise JsonError("Corrupt number.")
281 while True:
282 data += ch
283 ch = reader.get()
285 if ch not in "0123456789":
286 if ch != '':
287 reader.unget()
289 break
291 return data
293func _decode_fraction(reader: StringReader) -> string?:
294 if reader.peek() != '.':
295 return None
297 reader.get()
298 data = "."
299 data += _decode_digits(reader)
301 return data
303func _decode_exponent(reader: StringReader) -> string:
304 return ""
306func _decode_true(reader: StringReader) -> Bool:
307 if reader.read(3) != "rue":
308 raise JsonError("Corrupt true.")
310 return Bool(True)
312func _decode_false(reader: StringReader) -> Bool:
313 if reader.read(4) != "alse":
314 raise JsonError("Corrupt false.")
316 return Bool(False)
318func _decode_null(reader: StringReader) -> Null:
319 if reader.read(3) != "ull":
320 raise JsonError("Corrupt null.")
322 return Null()
324func decode(data: string) -> Value:
325 """Decode given JSON string.
327 """
329 reader = StringReader(data)
330 root: Value? = None
331 stack: [_StackValue] = []
332 value: Value? = None
333 stack_value: _StackValue? = None
335 while True:
336 stack_value = None
337 ch = reader.get()
339 if ch == '{':
340 value = Object()
341 stack_value = _StackObject(value)
342 elif ch == '[':
343 value = List()
344 stack_value = _StackList(value)
345 elif ch in "}]":
346 stack.pop().finalize(ch)
347 continue
348 elif ch in "\t\r\n ":
349 continue
350 elif ch == ':':
351 stack[-1].handle_colon()
352 continue
353 elif ch == ',':
354 stack[-1].handle_comma()
355 continue
356 elif ch == '\"':
357 value = _decode_string(reader)
358 elif ch in "-0123456789":
359 value = _decode_number(reader, ch)
360 elif ch == 't':
361 value = _decode_true(reader)
362 elif ch == 'f':
363 value = _decode_false(reader)
364 elif ch == 'n':
365 value = _decode_null(reader)
366 elif ch == '':
367 break
368 else:
369 raise JsonError(f"Invalid character '{ch}'")
371 if root is None:
372 root = value
373 elif stack.length() > 0:
374 stack[-1].handle_value(value)
375 else:
376 raise JsonError("Unexpected value.")
378 if stack_value is not None:
379 stack.append(stack_value)
381 if stack.length() != 0:
382 raise JsonError("Missing object or list end.")
383 elif root is None:
384 raise JsonError("No root value found.")
386 return root
388test single_value():
389 encoded = "{}"
390 decoded = decode(encoded)
391 assert decoded.object() == {}
393 encoded = "[]"
394 decoded = decode(encoded)
395 assert decoded.list() == []
397 encoded = "\"hi\""
398 decoded = decode(encoded)
399 assert decoded.string() == "hi"
401 encoded = "1"
402 decoded = decode(encoded)
403 assert decoded.integer() == 1
405 encoded = "2.0"
406 decoded = decode(encoded)
407 assert decoded.float() == 2.0
409 encoded = "-2.0"
410 decoded = decode(encoded)
411 assert decoded.float() == -2.0
413 encoded = "20.05"
414 decoded = decode(encoded)
415 assert decoded.float() == 20.05
417 encoded = "false"
418 decoded = decode(encoded)
419 assert not decoded.bool()
421 encoded = "true"
422 decoded = decode(encoded)
423 assert decoded.bool()
425 encoded = "null"
426 decoded = decode(encoded)
427 decoded.null()
429test various():
430 encoded = "{\"a\": true}"
431 decoded = decode(encoded)
432 assert decoded.get("a").bool()
434 encoded = "{\"b\": false, \"c\": []}"
435 decoded = decode(encoded)
436 assert not decoded.get("b").bool()
437 assert decoded.get("c").list().length() == 0
439 encoded = "{\"d\": [1, null]}"
440 decoded = decode(encoded)
441 assert decoded.get("d").at(0).integer() == 1
442 decoded.get("d").at(1).null()
444 match decoded.get("d").at(1):
445 case Null():
446 pass
447 case _:
448 assert False
450test nested_objects_and_lists():
451 encoded = "{\"a\": [true, [4, 2, 3], {\"f\": {\"b\": 1, \"c\": null}}, 55]}"
452 decoded = decode(encoded)
453 assert decoded.get("a").at(0).bool()
454 assert decoded.get("a").at(1).at(0).integer() == 4
455 assert decoded.get("a").at(1).at(1).integer() == 2
456 assert decoded.get("a").at(1).at(2).integer() == 3
457 assert decoded.get("a").at(2).get("f").get("b").integer() == 1
458 decoded.get("a").at(2).get("f").get("c").null()
459 assert decoded.get("a").at(3).integer() == 55
461test missing_closing_brace():
462 encoded = "{"
464 try:
465 message = ""
466 decode(encoded)
467 except JsonError as e:
468 message = e.message
470 assert message == "Missing object or list end."
472test missing_closing_bracket():
473 encoded = "["
475 try:
476 message = ""
477 decode(encoded)
478 except JsonError as e:
479 message = e.message
481 assert message == "Missing object or list end."
483test missing_string_end():
484 encoded = "\"asdasd"
486 try:
487 message = ""
488 decode(encoded)
489 except JsonError as e:
490 message = e.message
492 assert message == "Out of data when parsing string."
494test corrupt_null():
495 encoded = "nuls"
497 try:
498 message = ""
499 decode(encoded)
500 except JsonError as e:
501 message = e.message
503 assert message == "Corrupt null."
505test corrupt_null_short():
506 encoded = "nul"
508 try:
509 message = ""
510 decode(encoded)
511 except JsonError as e:
512 message = e.message
514 assert message == "Corrupt null."
516test corrupt_true():
517 encoded = "truu"
519 try:
520 message = ""
521 decode(encoded)
522 except JsonError as e:
523 message = e.message
525 assert message == "Corrupt true."
527test corrupt_false():
528 encoded = "fALSE"
530 try:
531 message = ""
532 decode(encoded)
533 except JsonError as e:
534 message = e.message
536 assert message == "Corrupt false."