Report the first invalid JSON Lines record number
To report the first invalid JSON Lines record number, iterate over the text stream with enumerate(..., start=1) and call json.loads() for each line. A whole-text json.loads() call only sees one JSON document; with this fixture, it reports Extra data after the first valid object instead of identifying the malformed second record. json.loads raises JSONDecodeError for invalid JSON, and that exception includes parsing-location attributes, but the stream loop supplies the record number directly.
The fixture deliberately contains valid, malformed, valid records. First, the code shows the unhelpful whole-file error. Next, it starts again with StringIO, accepts record 1, encounters {"id": } on record 2, prints that record number, and breaks. The final list demonstrates that record 3 was not decoded after the first error.
This is a fail-fast validator, not an error collector. Blank lines would also cause json.loads() to fail unless the code defines a separate blank-line policy. Likewise, “record number” here means physical input line number, so multi-line JSON values do not fit this simple JSON Lines contract. JSONDecodeError was added in Python 3.5; no newer API is required.
AI assistance disclosure: the example was AI-assisted and executed only against the shown in-memory fixture.
Example
import json
from io import StringIO
text = '{"id": 1}\n{"id": }\n{"id": 3}\n'
print(repr(text))
try:
json.loads(text)
except json.JSONDecodeError as error:
print(f"whole-file error: {error.msg}")
accepted = []
for record_number, line in enumerate(StringIO(text), start=1):
try:
accepted.append(json.loads(line))
except json.JSONDecodeError:
print(f"first invalid record: {record_number}")
break
assert accepted == [{"id": 1}]
print(accepted)
Expected output:
'{"id": 1}\n{"id": }\n{"id": 3}\n'
whole-file error: Extra data
first invalid record: 2
[{'id': 1}]