Skip to content

Format review guide

JSON Lines Dataset Review: Check Every Record Without Losing Line Context

Review a JSON Lines export one physical line at a time. Separate encoding and syntax failures from record shape, schema, privacy, and rights questions before interpreting the data.

JSON Lines is easy to stream and easy to misread. A file can contain one JSON value per line while still mixing record shapes, silently accepting duplicate keys, or using a line convention that the receiving process does not share. Review the physical lines first, then decide whether the values satisfy the dataset contract. This is a format review method, not a quality guarantee, privacy clearance, or licensing decision.

Updated

On this page

Establish the file contract before parsing

The source dates shown in this guide identify documentation checks on 22 October 2026, not original publication dates. Follow each citation for the standard or documentation edition being discussed.

The jsonlines.org, (opens in a new tab) describes UTF-8, one valid JSON value on each line, and LF line terminators. CRLF is accepted because JSON whitespace can surround the value. A final line terminator is recommended but not required, a blank line is not a JSON value, and a byte order mark must not be included. The .jsonl extension is a convention, not a semantic guarantee.

Establish the file contract before parsing (table)
Contract itemRecord before the first row is interpretedOpen question
Byte sourceRelative artifact identifier, byte size, and digest or controlled referenceIs this the exact artifact named by the review?
Text ruleUTF-8 expectation, BOM policy, and compressed wrapperWas the wrapper separated without changing the source?
Line ruleLF, accepted CRLF, final-newline policy, and blank-line policyWhat does the producer treat as a record boundary?
Record ruleObject per line, or an explicit allowance for other JSON valuesIs this dataset contract narrower than JSON Lines?
Schema ruleRequired keys, allowed types, version field, and unknown-key policyWhich producer schema governs the review?
Identity ruleField or combination identifying a record, or explicit unknownCan duplicate identifiers be detected?
Failure ruleStop, quarantine, or reconcile policy for a bad lineWho decides whether a derivative is allowed?

Compression is a transport or packaging decision, not a change to the record grammar. If the delivered artifact is sample.jsonl.gz, retain the compressed-file digest and record the decompressed review artifact separately. Do not call a decompressed derivative the original file.

Parse one physical line at a time with bounded diagnostics

The useful diagnostic unit is the physical line, not an arbitrarily sampled JSON object. Keep the line number, stable issue class, and source artifact reference. Do not store a raw line, key, value, or parser message in a public report when it may contain a name, token, location, or confidential text.

Bounded JSON Lines helpers

Parse one physical line at a time with bounded diagnostics code example, Python
import json
import math
from pathlib import Path


class RecordIssue(ValueError):
    def __init__(self, kind: str):
        super().__init__(kind)
        self.kind = kind


def no_duplicate_names(pairs):
    record = {}
    for key, value in pairs:
        if key in record:
            raise RecordIssue("duplicate_key")
        record[key] = value
    return record


def positive_limit(value, name):
    if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
        raise RecordIssue("invalid_limit")
    return value


def reject_constant(_value: str):
    raise RecordIssue("non_finite_number")


def bounded_int(value: str):
    try:
        return int(value)
    except (OverflowError, ValueError):
        raise RecordIssue("numeric_limit")


def finite_float(value: str):
    try:
        number = float(value)
    except (OverflowError, ValueError):
        raise RecordIssue("numeric_limit")
    if not math.isfinite(number):
        raise RecordIssue("non_finite_number")
    return number

Pre-parse nesting guard

Parse one physical line at a time with bounded diagnostics code example, Python
def scan_depth(text: str, max_depth: int):
    stack = []
    quoted = False
    escaped = False
    for character in text:
        if quoted:
            if escaped:
                escaped = False
            elif character == "\\":
                escaped = True
            elif character == '"':
                quoted = False
            continue
        if character == '"':
            quoted = True
        elif character in "[{":
            stack.append(']' if character == '[' else '}')
            if len(stack) > max_depth:
                raise RecordIssue("nesting_limit")
        elif character in "]}":
            if not stack or stack.pop() != character:
                raise RecordIssue("invalid_nesting")
    return len(stack)

Bounded line inspection

Parse one physical line at a time with bounded diagnostics code example, Python
def inspect_line(raw_line: bytes, line_number: int, max_depth: int):
    if raw_line.endswith(b"\r") and not raw_line.endswith(b"\r\n"):
        return {"line": line_number, "kind": "invalid_line_ending"}
    if line_number == 1 and raw_line.startswith(b"\xef\xbb\xbf"):
        return {"line": line_number, "kind": "bom"}
    if raw_line.strip() == b"":
        return {"line": line_number, "kind": "blank_line"}
    try:
        text = raw_line.decode("utf-8", errors="strict")
        scan_depth(text, max_depth)
        value = json.loads(text, object_pairs_hook=no_duplicate_names,
                           parse_constant=reject_constant,
                           parse_int=bounded_int, parse_float=finite_float)
        if not isinstance(value, dict):
            return {"line": line_number, "kind": "record_not_object"}
    except UnicodeDecodeError:
        return {"line": line_number, "kind": "invalid_utf8"}
    except RecordIssue as error:
        return {"line": line_number, "kind": error.kind}
    except RecursionError:
        return {"line": line_number, "kind": "resource_limit"}
    except json.JSONDecodeError as error:
        return {"line": line_number, "kind": "invalid_json", "char_offset": error.pos}
    return None

Bounded JSON Lines inspection

Parse one physical line at a time with bounded diagnostics code example, Python
def inspect_jsonl(path: Path, max_line_bytes=1_000_000, max_issues=100,
                 max_depth=100):
    max_line_bytes = positive_limit(max_line_bytes, "max_line_bytes")
    max_issues = positive_limit(max_issues, "max_issues")
    max_depth = positive_limit(max_depth, "max_depth")
    issues = []

    def add(issue):
        issues.append(issue)
        return len(issues) >= max_issues

    with path.open("rb") as stream:
        line_number = 0
        while True:
            raw_line = stream.readline(max_line_bytes + 1)
            if raw_line == b"":
                break
            line_number += 1
            if len(raw_line) > max_line_bytes:
                while raw_line and not raw_line.endswith((b"\n", b"\r\n")):
                    raw_line = stream.readline(max_line_bytes + 1)
                if add({"line": line_number, "kind": "line_too_long"}):
                    return issues
                continue
            issue = inspect_line(raw_line, line_number, max_depth)
            if issue is not None and add(issue):
                return issues
    return issues

The helper, depth guard, line, and main blocks form one bounded example. All three limits must be positive integers, and booleans are rejected explicitly because Python treats them as integers. The numeric hooks turn an integer-conversion limit into numeric_limit and reject ordinary exponential overflow as non_finite_number; parse_constant handles named non-finite constants. The raw line check rejects a bare carriage return while allowing LF, CRLF, and an unterminated final JSON value. The scanner tracks brackets outside escaped quoted strings before parsing, so a deeply nested document can receive a stable nesting_limit issue and a bracket in a string is not treated as structure. RecursionError becomes resource_limit without exception text. The example reads at most max_line_bytes plus one byte, drains an oversized physical line before continuing, and stops at max_issues. It emits only stable issue kinds and offsets, never a raw key, value, or parser error string. This is not a hostile-input sandbox; external CPU, memory, and process-time limits are still needed.

Synthetic edge cases

Parse one physical line at a time with bounded diagnostics code example, Plain text
synthetic-nan -> non_finite_number
synthetic-infinity -> non_finite_number
synthetic-duplicate -> duplicate_key
nan -> non_finite_number
infinity -> non_finite_number
nested_duplicate -> duplicate_key
very_large_integer -> numeric_limit
exponential_overflow -> non_finite_number
blank_whitespace -> blank_line
nonobject -> record_not_object
invalid_utf8 -> invalid_utf8
bare_carriage_return -> invalid_line_ending
oversized_line -> line_too_long
deep_nesting -> nesting_limit
brackets_inside_string -> no_nesting_issue
max_line_bytes_negative -> invalid_limit
max_issues_zero -> invalid_limit
max_depth_bool -> invalid_limit

The synthetic cases cover non-finite numbers, duplicate names, whitespace-only input, a scalar where this dataset contract requires an object, invalid UTF-8, an oversized physical line, deep nesting, a bracket sequence inside a quoted string, and explicit negative, zero, or boolean limit values for the three bounded controls. Keep fixtures synthetic and bounded. Do not use real names, messages, addresses, or locations to demonstrate that the parser ran.

Apply record semantics only after syntax passes

JSON Lines permits any JSON value on a line. A dataset may choose a narrower contract, such as one object with a string identifier and an integer schema version. That choice belongs in the dataset contract, not in the format name. RFC 8259 also recommends unique object member names because duplicate names can make receiver behavior unpredictable Internet Engineering Task Force, (opens in a new tab).

Synthetic record fixture

Apply record semantics only after syntax passes code example, JSON
{
  "record_id": "synthetic-001",
  "schema_version": 1,
  "event": "created",
  "occurred_at": "2026-01-02T03:04:05Z"
}
Apply record semantics only after syntax passes (table)
FieldSynthetic contractReview question
record_idNon-empty string, unique within the declared exportIs uniqueness tested, or only assumed?
schema_versionInteger selected by the producerDoes a reader know which rules apply?
eventOne value from a documented codebookAre unknown labels held for review?
occurred_atRFC-style instant or explicit null stateDoes null mean unavailable, not applicable, or error?

Do not silently coerce a string "1" to an integer, an empty string to null, or a missing key to a default unless the contract names that transformation. A normalized derivative needs its own artifact reference and digest.

Use a disposition table for failures

Use a disposition table for failures (table)
ObservationSafe interpretationNext action
First bytes contain a BOMThe source is not in the stated JSON Lines byte formPreserve the source and decide whether a documented derivative is allowed
Blank or whitespace-only physical lineNo JSON value exists on that lineReconcile the producer rule; do not count it as a record
UTF-8 decode failureThe declared text contract is not satisfied thereAsk for the source encoding or corrected export
JSON parse failureThe line is not one valid JSON value under the parserKeep line number, stable class, and safe offset; do not repair silently
Valid scalar where an object is requiredFormat syntax passed but dataset shape failedRecord a schema-shape failure
Duplicate object nameThe same member name occurs more than oncePreserve the line and require an owner decision
NaN or Infinity constantA non-standard numeric constant was presentedReject it or document a producer-specific contract
Physical line exceeds the configured limitReview policy cannot safely process the lineRecord line_too_long and stop or quarantine it
Object parses but required key is missingSyntax passed; record contract failedClassify missingness and compare the producer schema
All checks passThe selected format and shape checks have evidenceContinue provenance, privacy, rights, and accuracy review

Do not collapse findings into a single “clean” score. Valid syntax can coexist with incomplete vocabulary, duplicate identifiers, or changed field meaning. Keep each finding in a separate column so a later reader can tell what was checked.

Keep diagnostics safe and the next step qualified

For an export that may contain sensitive records, a public worksheet can contain the file reference, line number, issue class, schema version, and stable evidence reference. It should not copy a message, token, address, precise location, or raw row merely to prove that parsing ran. If a redacted excerpt is needed, record approval, the transformation, and remaining uncertainty.

Pair this check with the data provenance log and broader export guidance. The provenance log owns source, rights, processing, and exclusion evidence. Export guidance owns tool-specific quality. This guide owns line-level JSON diagnostics.

After the owner has a supported non-health scope, authority to describe the collection, and a metadata or synthetic review packet, the qualified appraisal request path may be a next preparation step. It does not validate JSON Lines, accept a dataset, create a quote, match a buyer, issue a legal opinion, or grant a license. Keep raw sensitive records outside this guide and within the owner-approved handling boundary.

Takeaways

  • Treat UTF-8, one value per physical line, newline behavior, and BOM handling as explicit contract items.
  • Bound line size and issue count, and emit stable diagnostics without raw keys, values, or parser messages.
  • Parse and report line failures before deciding whether records satisfy a dataset schema.
  • Use synthetic or metadata-only fixtures before exposing raw records.
  • Keep syntax, schema, provenance, privacy, rights, accuracy, and demand as separate questions.

Sources

  1. jsonlines.org: JSON Lines (opens in a new tab)
  2. Internet Engineering Task Force: The JavaScript Object Notation (JSON) Data Interchange Format (opens in a new tab)
  3. Python Software Foundation: json, JSON encoder and decoder (opens in a new tab)

Use the appraisal path after the source and bounded review packet are organized.

It does not validate JSON Lines, accept a dataset, create a quote, match a buyer, or grant a license.