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
| Check | Reason |
|---|---|
| Root type | A list of records and one dictionary need different iteration |
| Record count | A parse success alone can hide lost records |
| Known field values | The output should preserve the intended data |
| Last record and trailing content | The 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.