pythonjsonformatter

Read JSON Lines in Python and locate a broken record

An export with one JSON value per line needs a record-by-record reader, not one json.loads() call for the entire file.

Recognize JSON Lines before repairing syntax

Two objects on separate lines are not one JSON array. They can be a JSON Lines stream, commonly saved with a .jsonl extension. The extension is only a clue: confirm the export format with the producing system.

{"event":"created","id":41}
{"event":"updated","id":41}

Calling json.loads() once on this whole text reports extra data after the first document. Adding or removing a trailing comma is not the underlying fix. In JSON Lines, each line must independently contain a valid JSON value. A single line can contain an array, string, or null, although many applications require each record to be an object.

Read a small stream and report the physical line

This runnable example uses an in-memory file so the failure and line number are reproducible. The second record intentionally contains uppercase True, which is Python syntax rather than JSON:

import io
import json

stream = io.StringIO(
    '{"event":"created","id":41}\n'
    '{"event":"updated","active":True}\n'
)
for line_number, line in enumerate(stream, start=1):
    try:
        record = json.loads(line)
    except json.JSONDecodeError as exc:
        print(f"Line {line_number}, column {exc.colno}: invalid JSON")
        break
    if not isinstance(record, dict):
        raise ValueError(f"Line {line_number}: expected an object")
    print(f"Line {line_number}: {record['event']}")

Expected output:

Line 1: created
Line 2, column 29: invalid JSON

For a real file, replace StringIO with a file opened using encoding="utf-8" inside a with block. The object check is an application rule in this example, not a JSON Lines requirement.

Write one complete value per line

Serialize each record compactly and append a newline. Pretty-printing a record over several lines breaks the one-record-per-line convention. Repeated json.dump() calls without a delimiter do not create a valid single JSON document either.

import json

records = [{"id": 41}, {"id": 42}]
text = "".join(json.dumps(record, separators=(",", ":")) + "\n" for record in records)
print(text, end="")

Output:

{"id":41}
{"id":42}

A final newline is useful for appending and concatenating records. Blank lines are not valid JSON values: decide whether to reject them or explicitly document a more permissive import policy. Do not silently skip malformed records if the count or order matters.

Inspect a record without loading the entire export

The site's Viewer and Diff Checker expect a single JSON document per input. Paste one complete JSONL record to inspect it. The tools do not currently split an entire JSONL stream into records for you.

Reading line by line avoids retaining the whole stream, but one unusually large line can still use substantial memory. For untrusted exports, impose record-size and resource limits before parsing. If the consumer needs an array, converting all records into a list is a separate operation with its own memory cost.

See the JSON Lines format requirements for UTF-8 encoding and line rules, and the Python json reference for decoder behavior.

By Python JSON Formatter ·