Read a damaged JSON sequence without pretending it is complete
The short answer
Recoverability and completeness are different properties. Give a damaged record a visible place in the result.
Recovery is a product decision
A client receiving multiple JSON records must first know the agreed framing format. A JSON array, separate documents per line, and a JSON text sequence are not interchangeable files. The question here is how to inspect usable parts of a sequence without making damage invisible.
RFC 7464 describes UTF-8 JSON text sequences and recovery after damaged elements; consecutive RS bytes do not denote empty elements.[5] The exercise ignores empty pieces between separators, including after a trailing RS. Local acceptance requires LF after each nonempty candidate and an object with textual id. That object profile and LF requirement are not the complete parser grammar.
Make a local parse failure visible
- accepted_ids contains only objects passing this local profile.
- gaps contains local nonempty parse-candidate ordinals, not reliable source record positions.
- partial_hold disallows claiming a complete export in this exercise.
- framing_ok only means this framing profile found no gap; it does not prove an expected record set.
Execute the literal damaged input
The byte fixture below places an RS before each element. An incomplete object sits between intact objects with id A and C. The code receives the complete byte buffer, at most 4096 bytes, and sends nothing. Its goal is a visible gap ledger, not repairing a file by inserting invented contents.
Use Copy code to copy the complete code exactly. Manual selection may omit the final newline.
import json
def recover(raw):
if type(raw) is not bytes or len(raw) not in range(4097) or (raw and not raw.startswith(b"\x1e")):
raise ValueError("unsupported sequence envelope")
accepted, gaps = [], []
# Consecutive/trailing RS bytes do not create empty sequence elements.
candidates = (chunk for chunk in raw.split(b"\x1e")[1:] if chunk)
for number, chunk in enumerate(candidates, 1):
try:
if not chunk.endswith(b"\n"):
raise ValueError("missing final LF under local policy")
value = json.loads(chunk.decode("utf-8"))
if type(value) is not dict or type(value.get("id")) is not str:
raise ValueError("this example requires an object with text id")
except (ValueError, UnicodeError):
gaps.append(number)
else:
accepted.append(value["id"])
return {"accepted_ids": accepted, "gaps": gaps,
"state": "partial_hold" if gaps else "framing_ok"}
# Literal RS bytes surround one broken object and two intact objects.
fixture = b'\x1e{"id":"A"}\n\x1e{"id":\n\x1e{"id":"C"}\n'
result = recover(fixture)
assert result["accepted_ids"] == ["A", "C"] and result["gaps"] == [2]
assert recover(b'\x1e{"id":"A"}')["gaps"] == [1]
assert recover(b'\x1etrue\n')["gaps"] == [1]
assert recover(b'\x1e{"id":1}\n')["gaps"] == [1]
for bad in [b'{"id":"A"}\n', b' ' * 4097]:
try:
recover(bad)
except ValueError:
pass
else:
raise AssertionError("unsupported envelope accepted")
assert recover(b'\x1e\x1e{"id":"A"}\n\x1e')["gaps"] == []
assert recover(b'\x1e\x1e')["gaps"] == []
print(json.dumps({"recovery": result, "additional_gap_cases": 3, "envelope_rejections": 2,
"separator_run_cases": 2}, sort_keys=True))
{"additional_gap_cases": 3, "envelope_rejections": 2, "recovery": {"accepted_ids": ["A", "C"], "gaps": [2], "state": "partial_hold"}, "separator_run_cases": 2}
The actual local output retains A and C and reports local candidate position 2. This is not a reliable source record number: bytes may be missing, extra RS may split candidates, and separator runs do not count. Additional assertions hold an object without final LF, a boolean, and a numeric id. Two envelope fixtures reject a missing initial RS and an oversized buffer. Two separator fixtures show that repeated and trailing RS invent no gaps. This does not simulate a network interruption; only preassembled bytes were processed.
Decide separately what partial usability means
An inspection view can show intact records with a prominent partial-result notice. A consumer requiring a complete export must hold the whole result and follow the agreed recovery route. The same parse result can therefore lead to different product actions. Do not delete gaps to obtain a successful counter or call accepted objects a complete source copy.
This is not a streaming implementation: chunk boundaries, backpressure, and intermediate persistence were not tested. Duplicate JSON names, duplicate ids, application schemas, and non-finite numbers need separate checks. The example performs no import, retry, or write and proves no record completeness. Source research and synthetic local execution were completed; access, rights, and independent factual and translation review are not part of this result.
Sources & evidence
- Original source title: RFC 7464 — JavaScript Object Notation (JSON) Text Sequences
Accessed: 2026-10-18T22:58:31.189040+00:00