json1-1 Exercise 3: Fixing a Broken File One Error at a Time ============================================================ --- ex3.py (Python 3.14.0) --- import json broken = """{ // the shop's config 'name': "Corner Shop", "open": True, "hours": [9, 17,], "discount": 010, "owner": {"first": "Ada" "last": "Lovelace"} } """ fixes = [ (" // the shop's config\n", ""), ("'name'", '"name"'), ("True", "true"), ("[9, 17,]", "[9, 17]"), ("010", "10"), ('"Ada" "last"', '"Ada", "last"'), ] text = broken n = 0 while True: try: data = json.loads(text) print("round %d: valid -> %r" % (n, data)) break except json.JSONDecodeError as e: line = text.splitlines()[e.lineno - 1] print("round %d: line %d col %d: %s" % (n, e.lineno, e.colno, e.msg)) print(" " + line) print(" " + " " * (e.colno - 1) + "^") find, repl = fixes[n] text = text.replace(find, repl, 1) n += 1 Output: round 0: line 2 col 3: Expecting property name enclosed in double quotes // the shop's config ^ round 1: line 2 col 3: Expecting property name enclosed in double quotes 'name': "Corner Shop", ^ round 2: line 3 col 11: Expecting value "open": True, ^ round 3: line 4 col 18: Illegal trailing comma before end of array "hours": [9, 17,], ^ round 4: line 5 col 16: Expecting ',' delimiter "discount": 010, ^ round 5: line 6 col 28: Expecting ',' delimiter "owner": {"first": "Ada" "last": "Lovelace"} ^ round 6: valid -> {'name': 'Corner Shop', 'open': True, 'hours': [9, 17], 'discount': 10, 'owner': {'first': 'Ada', 'last': 'Lovelace'}} --- python -m json.tool --- ok.json : {"name":"Ada","tags":["x","y"],"n":{"k":null}} bad.json : {"a":1, "b":[1,2,} py -m json.tool ok.json (exit status 0) { "name": "Ada", "tags": [ "x", "y" ], "n": { "k": null } } py -m json.tool bad.json (exit status 1) Expecting value: line 2 column 11 (char 18) WHY THIS WORKS AS AN ANSWER --------------------------- The loop reports only the first error each round, which is how a parser works, and it fixes exactly one thing before trying again, so the six mistakes show up in order and the count of rounds (6, then valid on round 6) matches the six mistakes I planted. Two things to notice in the output: rounds 0 and 1 give the same message at the same position (the next problem sat where the first one had been), and for 010 Python points at column 16, the "1" after the zero, not at the zero: a parser reports where it gave up, which can be a character after the real mistake. The json.tool runs show the command-line version of the same check: exit status 0 and indented output for a valid file, a message and exit status 1 for an invalid one. Python is only the checking tool here; the file is plain JSON and any language can read it once it is fixed.