APIs, open data & integration

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.

What fits your situation? Answer 30 short questions →

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

  1. Original source title: RFC 7464 — JavaScript Object Notation (JSON) Text Sequences

    Accessed: 2026-10-18T22:58:31.189040+00:00

A guide for your situation

Your answers stay in this page until you explicitly submit them. Unsaved answers disappear on reload.

Saving is not configured. No answers or contact details are submitted or saved.

Which integration step helps you move forward?

30 short questions about APIs and connecting data. Selecting an answer moves to the next question. You can go back, change answers, or skip. This is a practical reading route, not a scientific test.

JavaScript is required for this guide.

Contact & corrections

Send a question or report a correction using this form.

Only after Send are your name, email, subject and message sent to the contact service to handle your message. This is not a newsletter signup. Unsent text disappears on reload. Privacy information

Preview: sending is not configured. This form does not send or save anything.

Your message — all fields are required