Hide keyboard shortcuts

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 

2 

3class JsonError(Error): 

4 message: string 

5 

6trait Value: 

7 """A value in a JSON document. 

8 

9 """ 

10 

11 func object(self) -> {string: Value}: 

12 """As an object. Raises an error for non-object values. 

13 

14 """ 

15 

16 raise NotImplementedError() 

17 

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. 

21 

22 """ 

23 

24 raise NotImplementedError() 

25 

26 func list(self) -> [Value]: 

27 """As a list. Raises an error for non-list values. 

28 

29 """ 

30 

31 raise NotImplementedError() 

32 

33 func at(self, index: i64) -> Value: 

34 """As an item in a list. Raises an error for non-list values. 

35 

36 """ 

37 

38 raise NotImplementedError() 

39 

40 func string(self) -> string: 

41 """As a string. Raises an error for non-string values. 

42 

43 """ 

44 

45 raise NotImplementedError() 

46 

47 func integer(self) -> i64: 

48 """As an integer. Raises an error for non-integer values. 

49 

50 """ 

51 

52 raise NotImplementedError() 

53 

54 func float(self) -> f64: 

55 """As a float. Raises an error for non-float values. 

56 

57 """ 

58 

59 raise NotImplementedError() 

60 

61 func bool(self) -> bool: 

62 """As a boolean. Raises an error for non-bool values. 

63 

64 """ 

65 

66 raise NotImplementedError() 

67 

68 func null(self): 

69 """As null. Raises an error for non-null values. 

70 

71 """ 

72 

73 raise NotImplementedError() 

74 

75class Object(Value): 

76 """An object. 

77 

78 """ 

79 

80 items: {string: Value} 

81 

82 func __init__(self): 

83 self.items = {} 

84 

85 func object(self) -> {string: Value}: 

86 return self.items 

87 

88 func get(self, key: string) -> Value: 

89 return self.items[key] 

90 

91class List(Value): 

92 """A list. 

93 

94 """ 

95 

96 items: [Value] 

97 

98 func __init__(self): 

99 self.items = [] 

100 

101 func list(self) -> [Value]: 

102 return self.items 

103 

104 func at(self, index: i64) -> Value: 

105 return self.items[index] 

106 

107class String(Value): 

108 """A string. 

109 

110 """ 

111 

112 value: string 

113 

114 func string(self) -> string: 

115 return self.value 

116 

117class Integer(Value): 

118 """An integer. 

119 

120 """ 

121 

122 value: i64 

123 

124 func integer(self) -> i64: 

125 return self.value 

126 

127class Float(Value): 

128 """A float. 

129 

130 """ 

131 

132 value: f64 

133 

134 func float(self) -> f64: 

135 return self.value 

136 

137class Bool(Value): 

138 """A boolean. 

139 

140 """ 

141 

142 value: bool 

143 

144 func bool(self) -> bool: 

145 return self.value 

146 

147class Null(Value): 

148 """Null. 

149 

150 """ 

151 

152 func null(self): 

153 pass 

154 

155trait _StackValue: 

156 

157 func handle_value(self, value: Value): 

158 pass 

159 

160 func handle_comma(self): 

161 self.expecting_another_item = True 

162 

163 func handle_colon(self): 

164 raise NotImplementedError() 

165 

166 func finalize(self, ch: char): 

167 pass 

168 

169class _StackObject(_StackValue): 

170 value: Object 

171 key: String? 

172 expecting_another_item: bool 

173 

174 func __init__(self, value: Object): 

175 self.value = value 

176 self.key = None 

177 self.expecting_another_item = False 

178 

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 

186 

187 func handle_colon(self): 

188 if self.key is None: 

189 raise NotImplementedError() 

190 

191 func finalize(self, ch: char): 

192 if self.key is not None: 

193 raise NotImplementedError() 

194 

195 if self.expecting_another_item: 

196 raise NotImplementedError() 

197 

198 if ch != '}': 

199 raise NotImplementedError() 

200 

201class _StackList(_StackValue): 

202 value: List 

203 expecting_another_item: bool 

204 

205 func __init__(self, value: List): 

206 self.value = value 

207 self.expecting_another_item = False 

208 

209 func handle_value(self, value: Value): 

210 self.value.items.append(value) 

211 self.expecting_another_item = False 

212 

213 func finalize(self, ch: char): 

214 if self.expecting_another_item: 

215 raise NotImplementedError() 

216 

217 if ch != ']': 

218 raise NotImplementedError() 

219 

220func _decode_string(reader: StringReader) -> String: 

221 value = "" 

222 

223 while True: 

224 ch = reader.get() 

225 

226 if ch == '\"': 

227 break 

228 elif ch == '\\': 

229 ch = reader.get() 

230 

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 

254 

255 return String(value) 

256 

257func _decode_number(reader: StringReader, ch: char) -> Value: 

258 data = "" 

259 

260 if ch == '-': 

261 data += ch 

262 else: 

263 reader.unget() 

264 

265 data += _decode_digits(reader) 

266 fraction = _decode_fraction(reader) 

267 exponent = _decode_exponent(reader) 

268 

269 if fraction is None: 

270 return Integer(i64(data + exponent)) 

271 else: 

272 return Float(f64(data + fraction + exponent)) 

273 

274func _decode_digits(reader: StringReader) -> string: 

275 data = "" 

276 ch = reader.get() 

277 

278 if ch not in "0123456789": 

279 raise JsonError("Corrupt number.") 

280 

281 while True: 

282 data += ch 

283 ch = reader.get() 

284 

285 if ch not in "0123456789": 

286 if ch != '': 

287 reader.unget() 

288 

289 break 

290 

291 return data 

292 

293func _decode_fraction(reader: StringReader) -> string?: 

294 if reader.peek() != '.': 

295 return None 

296 

297 reader.get() 

298 data = "." 

299 data += _decode_digits(reader) 

300 

301 return data 

302 

303func _decode_exponent(reader: StringReader) -> string: 

304 return "" 

305 

306func _decode_true(reader: StringReader) -> Bool: 

307 if reader.read(3) != "rue": 

308 raise JsonError("Corrupt true.") 

309 

310 return Bool(True) 

311 

312func _decode_false(reader: StringReader) -> Bool: 

313 if reader.read(4) != "alse": 

314 raise JsonError("Corrupt false.") 

315 

316 return Bool(False) 

317 

318func _decode_null(reader: StringReader) -> Null: 

319 if reader.read(3) != "ull": 

320 raise JsonError("Corrupt null.") 

321 

322 return Null() 

323 

324func decode(data: string) -> Value: 

325 """Decode given JSON string. 

326 

327 """ 

328 

329 reader = StringReader(data) 

330 root: Value? = None 

331 stack: [_StackValue] = [] 

332 value: Value? = None 

333 stack_value: _StackValue? = None 

334 

335 while True: 

336 stack_value = None 

337 ch = reader.get() 

338 

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}'") 

370 

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.") 

377 

378 if stack_value is not None: 

379 stack.append(stack_value) 

380 

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.") 

385 

386 return root 

387 

388test single_value(): 

389 encoded = "{}" 

390 decoded = decode(encoded) 

391 assert decoded.object() == {} 

392 

393 encoded = "[]" 

394 decoded = decode(encoded) 

395 assert decoded.list() == [] 

396 

397 encoded = "\"hi\"" 

398 decoded = decode(encoded) 

399 assert decoded.string() == "hi" 

400 

401 encoded = "1" 

402 decoded = decode(encoded) 

403 assert decoded.integer() == 1 

404 

405 encoded = "2.0" 

406 decoded = decode(encoded) 

407 assert decoded.float() == 2.0 

408 

409 encoded = "-2.0" 

410 decoded = decode(encoded) 

411 assert decoded.float() == -2.0 

412 

413 encoded = "20.05" 

414 decoded = decode(encoded) 

415 assert decoded.float() == 20.05 

416 

417 encoded = "false" 

418 decoded = decode(encoded) 

419 assert not decoded.bool() 

420 

421 encoded = "true" 

422 decoded = decode(encoded) 

423 assert decoded.bool() 

424 

425 encoded = "null" 

426 decoded = decode(encoded) 

427 decoded.null() 

428 

429test various(): 

430 encoded = "{\"a\": true}" 

431 decoded = decode(encoded) 

432 assert decoded.get("a").bool() 

433 

434 encoded = "{\"b\": false, \"c\": []}" 

435 decoded = decode(encoded) 

436 assert not decoded.get("b").bool() 

437 assert decoded.get("c").list().length() == 0 

438 

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() 

443 

444 match decoded.get("d").at(1): 

445 case Null(): 

446 pass 

447 case _: 

448 assert False 

449 

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 

460 

461test missing_closing_brace(): 

462 encoded = "{" 

463 

464 try: 

465 message = "" 

466 decode(encoded) 

467 except JsonError as e: 

468 message = e.message 

469 

470 assert message == "Missing object or list end." 

471 

472test missing_closing_bracket(): 

473 encoded = "[" 

474 

475 try: 

476 message = "" 

477 decode(encoded) 

478 except JsonError as e: 

479 message = e.message 

480 

481 assert message == "Missing object or list end." 

482 

483test missing_string_end(): 

484 encoded = "\"asdasd" 

485 

486 try: 

487 message = "" 

488 decode(encoded) 

489 except JsonError as e: 

490 message = e.message 

491 

492 assert message == "Out of data when parsing string." 

493 

494test corrupt_null(): 

495 encoded = "nuls" 

496 

497 try: 

498 message = "" 

499 decode(encoded) 

500 except JsonError as e: 

501 message = e.message 

502 

503 assert message == "Corrupt null." 

504 

505test corrupt_null_short(): 

506 encoded = "nul" 

507 

508 try: 

509 message = "" 

510 decode(encoded) 

511 except JsonError as e: 

512 message = e.message 

513 

514 assert message == "Corrupt null." 

515 

516test corrupt_true(): 

517 encoded = "truu" 

518 

519 try: 

520 message = "" 

521 decode(encoded) 

522 except JsonError as e: 

523 message = e.message 

524 

525 assert message == "Corrupt true." 

526 

527test corrupt_false(): 

528 encoded = "fALSE" 

529 

530 try: 

531 message = "" 

532 decode(encoded) 

533 except JsonError as e: 

534 message = e.message 

535 

536 assert message == "Corrupt false."