Desk Clarity

Everyday document & browser help

json.load reports Extra data after repeated dumps: choose one document boundary

By ON Lab · Sources checked November 8, 2026

Repeated json.dump(record, file) calls append separate serialized values; they do not automatically create one JSON array. If a later json.load encounters a second root value after the first, it can report Extra data. Choose a single array document or an explicit record-per-line format before changing the reader.

The historical reader showed a loop appending dictionaries and the resulting adjacent objects, then a load failure. We checked current Python JSON documentation and reproduced that exact framing class with two original tiny records. This is not a recovery method for every malformed file that happens to produce the same error.

Read the boundary, not only the error label

{"id": 1}{"id": 2}

Each object can be valid on its own, but the concatenation is not one ordinary JSON document. Whitespace or a newline between roots does not turn them into an array. In our local test, both adjacent roots and two newline-separated objects caused json.loads on the whole text to report Extra data.

The current JSON documentation notes that repeated serialization to the same file does not frame multiple objects into a valid single document. The useful question is what format the producer intended, not whether the filename ends in .json.

For one JSON document, serialize the collection once

import json

records = [{"id": 1}, {"id": 2}]
with open("records-new.json", "x", encoding="utf-8") as output:
    json.dump(records, output)

with open("records-new.json", encoding="utf-8") as source:
    restored = json.load(source)
assert restored == records

The array is the single root value. Mode x deliberately refuses to overwrite an existing path; choose a new output location for a real migration. The example shows the format contract and completion check. Our actual fixture used in-memory streams for repeated dumps and parsing, so it did not overwrite a user file.

Do not move file opening into a loop with mode w as a quick fix: repeatedly replacing the file would discard earlier records. Conversely, appending more dumps to an existing array does not insert elements into that array's syntax. Preserve the original source and create a reviewed replacement.

For a line-oriented stream, make the contract explicit

with open("records-new.jsonl", "x", encoding="utf-8") as output:
    for record in records:
        output.write(json.dumps(record) + "\n")

with open("records-new.jsonl", encoding="utf-8") as source:
    restored = [json.loads(line) for line in source]
assert restored == records

This simple example requires each physical line to contain one complete JSON value. It does not permit blank lines and does not pretty-print each record across several lines. Its reader validates the lines according to that deliberately chosen contract.

Our two one-line fixture records parsed into the same list as a single JSON array. That demonstrates two different valid handoff formats, not permission to guess a file's format solely from its line count. Keep the producer, reader and filename convention consistent.

Why line-by-line parsing can be the wrong repair

A single valid object can span multiple lines. In another fixture, the whole pretty-printed object parsed successfully, but parsing only its first line failed because that line did not contain a complete document. Splitting arbitrary input at every newline would break that valid representation.

Before selecting a line reader, inspect the source format specification and a complete boundary between records. If records are multi-line objects concatenated without an agreed framing scheme, ask the producer for a supported export or build a separately reviewed decoder for that actual format. This guide does not claim to repair arbitrary concatenated or truncated documents.

Verify every intended record

CheckReason
Root typeA list of records and one dictionary need different iteration
Record countA parse success alone can hide lost records
Known field valuesThe output should preserve the intended data
Last record and trailing contentThe reader must not silently stop after the first valid value

For a large input, choose a workflow appropriate to its size instead of assuming the in-memory list example is a streaming solution. Keep malformed-line reporting explicit. Ignoring every parse exception would make a partial import look complete.

If the error is instead an absent loads attribute, inspect the local json module shadowing guide. That concerns which module was imported. Extra data after successful decoding begins concerns the input document boundary.

Checked November 8, 2026. Two-root, JSON Lines, array and pretty-printed boundary examples were tested locally with Python 3.9.6. No original reader dataset, large-file recovery or performance guarantee was tested.

Sources