ScreenshotNeo

BlogComparisons

JSONL vs. JSON: Key Differences and Use Cases

JSON is one serialized value; JSONL is one JSON value per line. Learn which format fits APIs, logs, streams, files, and batch processing.

By the ScreenshotNeo team1 October 20269 min read

JSON represents one serialized value. JSONL (JSON Lines), also called newline-delimited JSON in many systems, represents a sequence of JSON values with one value per line. Use JSON when your data is naturally one document or one array. Use JSONL when independent records should be appended, streamed, piped through command-line tools, or processed one at a time.

Both formats use JSON syntax for each value. The key difference is the document boundary: JSON has one top-level value, while JSONL has many top-level values separated by line endings.

JSONL vs. JSON at a glance

Decision point JSON JSONL / NDJSON
Top-level organization One JSON value, commonly an object or array A sequence of JSON values, one per line
Typical processing Parse the document as a whole Parse or handle each record as it arrives
Appending Appending to an array requires maintaining valid brackets and commas Add another complete record on a new line
Common uses API requests and responses, configuration, nested documents Logs, exports, bulk records, shell pipelines, streams
Registered media type application/json Conventions vary: JSON Lines documents application/jsonl; NDJSON recommends application/x-ndjson

These are format tendencies, not promises about a particular library’s memory use or streaming behavior. Check the contract of the reader and writer you are integrating. JSON is defined as a text format for serializing structured data in RFC 8259. The line-oriented conventions are documented by JSON Lines and the NDJSON specification.

What JSON actually permits

RFC 8259 defines a JSON text as a serialized value. That value may be an object, array, string, number, boolean, or null. An object contains name/value pairs; an array contains ordered values.

{"event":"signup","user_id":42}
[{"id":1},{"id":2}]

Each example above is one complete JSON document. A parser can read it from a file, HTTP body, or message and produce one value.

What JSONL adds

JSONL is a convention for storing multiple JSON values in one text stream. Each record occupies one line:

{"event":"signup","user_id":42}
{"event":"purchase","user_id":42,"amount":19.99}
{"event":"logout","user_id":42}

Each line can be parsed independently. A producer can emit the first record before it has generated the rest, and a consumer can process records without constructing one giant array.

When to choose JSON

  • API request or response: a request body or response usually represents one resource or operation.
  • Configuration: nested objects and arrays are easier to validate as one document.
  • Atomic documents: the whole value must be accepted or rejected together.
  • Random access to nested data: document-oriented tools expect one root value.
  • Standards requiring JSON: send application/json when the API specifies it.

When to choose JSONL

  • Logs: each event is an independent record that can be tailed and indexed.
  • Large exports: process rows incrementally instead of loading an entire array.
  • Shell pipelines: line-oriented tools can filter, split, compress, and retry records.
  • Streaming: send records as they become available.
  • Append-heavy files: a writer can add a complete record without rewriting earlier records.
  • Bulk jobs: isolate failures to individual records when the application supports partial retry.

Runnable examples

Create and read JSON in Python

import json

payload = {
    "job": "thumbnail",
    "files": ["a.png", "b.png"]
}

with open("job.json", "w", encoding="utf-8") as f:
    json.dump(payload, f, ensure_ascii=False, indent=2)

with open("job.json", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["files"])

Write and stream JSONL in Python

import json

records = [
    {"id": 1, "status": "ready"},
    {"id": 2, "status": "queued"},
]

with open("records.jsonl", "w", encoding="utf-8", newline="\n") as f:
    for record in records:
        f.write(json.dumps(record, ensure_ascii=False) + "\n")

with open("records.jsonl", encoding="utf-8") as f:
    for line_number, line in enumerate(f, 1):
        if not line.strip():
            continue
        try:
            record = json.loads(line)
        except json.JSONDecodeError as exc:
            raise ValueError(f"invalid record on line {line_number}") from exc
        print(record["id"], record["status"])

Convert a JSON array to JSONL

import json

with open("input.json", encoding="utf-8") as source:
    values = json.load(source)

if not isinstance(values, list):
    raise TypeError("expected a top-level JSON array")

with open("output.jsonl", "w", encoding="utf-8", newline="\n") as target:
    for value in values:
        target.write(json.dumps(value, ensure_ascii=False) + "\n")

Read JSONL with Node.js

import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

const input = createInterface({
  input: createReadStream('records.jsonl', { encoding: 'utf8' }),
  crlfDelay: Infinity
});

for await (const line of input) {
  if (line.trim() === '') continue;
  const record = JSON.parse(line);
  console.log(record.id, record.status);
}

Send JSON with cURL

curl -X POST https://api.example.test/jobs \
  -H 'Content-Type: application/json' \
  --data '{"job":"thumbnail","priority":2}'

Send JSONL over HTTP with cURL

curl -X POST https://api.example.test/events \
  -H 'Content-Type: application/x-ndjson' \
  --data-binary $'{"event":"open"}\n{"event":"close"}\n'

Use the media type required by the receiving service. Do not assume that a server accepting application/json also accepts JSONL.

Line-ending, encoding, and validity rules

  • UTF-8: JSON Lines and NDJSON conventions require UTF-8. A byte-order mark should not be included.
  • Separators: LF (\n) is the normal separator; CRLF (\r\n) may be accepted. Configure readers explicitly when portability matters.
  • No raw newlines inside a record: put a newline in a JSON string as the escape sequence \n, not as a literal line break.
  • Malformed records: NDJSON says malformed JSON should cause an error. Decide whether your application stops, records a dead-letter line, or skips the record.
  • Blank lines: parsers may ignore them only when that behavior is documented. Make the policy explicit.
  • Trailing newline: emitting a final newline makes concatenation and command-line processing safer.
{"message":"line one\nline two"}

Appending safely and handling concurrency

JSONL makes the syntax of appending simple, but it does not make concurrent writes safe. Use one writer, an operating-system append mode with suitable locking, or a queue and single consumer. Flush or fsync according to your durability requirement. For high-volume logs, rotate files by size or time and include a stable event identifier for deduplication.

Appending to a JSON array is more complex: the writer must preserve opening and closing brackets, commas, and valid escaping. If multiple processes write at once, the document can become invalid.

Streaming, memory, and performance

A JSON parser that reads the whole document needs memory proportional to the document and may not produce the first value until the document is complete. A line reader can process JSONL incrementally, so peak memory can be close to the largest record plus parser overhead. This is an implementation property, not a guarantee of every library.

  • Keep records bounded; one enormous line removes the practical benefit of JSONL.
  • Use buffering and compression for throughput. Gzip works well because records remain independent after decompression.
  • Include a schema version in each record when producers and consumers may evolve separately.
  • Use backpressure in streaming clients so a fast producer cannot exhaust memory.
  • For parallel processing, partition by complete lines and preserve an ID for ordering or deduplication.

Schema design and error recovery

JSONL does not define a schema, ordering guarantee, transaction boundary, or retry policy. Define those at the application layer.

{"schema_version":1,"id":"evt_123","type":"invoice.paid","created_at":"2026-10-01T12:00:00Z","data":{}}
  1. Validate each record before applying side effects.
  2. Store the line number and record ID in error logs.
  3. Send invalid records to a dead-letter file or queue with the parse error.
  4. Make consumers idempotent so retries do not duplicate work.
  5. Document whether ordering is required and how a consumer detects gaps.

JSONL, NDJSON, and media types

The names are often used for the same practical representation, but their published conventions are not identical in every detail. JSON Lines documentation notes that application/jsonl is not standardized. NDJSON 1.0.0 recommends application/x-ndjson and the .ndjson extension. Match the exact format and media type expected by the receiving software instead of treating the labels as interchangeable.

Common errors and fixes

Error Cause Fix
Extra data from a JSON parser The input contains several top-level values, such as JSONL, but was parsed as one JSON document. Read line by line, or wrap the values in a valid JSON array.
Unexpected end of JSON input A record was truncated or a JSON document is missing a closing delimiter. Check transport completeness and write records atomically.
Everything appears on one line The producer emitted escaped \n text or omitted separators. Write an actual LF byte between records.
Parser fails on Windows files The reader does not handle CRLF. Enable universal newline handling or normalize \r\n to \n.
First record has invalid characters A UTF-8 byte-order mark was included. Write UTF-8 without BOM and remove it during ingestion if necessary.
HTTP 415 Unsupported Media Type The server expects a different content type. Use the documented Content-Type, such as application/json or application/x-ndjson.
Partial results after a failure JSONL processing is record-oriented and a later line failed. Track record IDs, make operations idempotent, and retry only failed records.

Or skip the browser setup

If your JSONL pipeline needs screenshots for each URL, ScreenshotNeo provides a single HTTP request instead of maintaining browser automation. It accepts consent banners before capture and removes 60+ known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Free accounts include 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.

Cost and reliability considerations

  • JSON is usually the simplest contract for a single request or response.
  • JSONL can reduce restart cost because completed records remain usable, but you need IDs, retries, and dead-letter handling.
  • Validate before side effects and record enough metadata to replay safely.
  • For external APIs, honor timeout, rate-limit, and retry guidance; the format itself provides no reliability guarantee.

FAQ

Can a JSON file contain multiple records?

Yes, put the records in one top-level array. That is still one JSON document, not JSONL.

Is JSONL faster than JSON?

Not inherently. JSONL enables incremental processing; speed depends on parser, I/O, record size, compression, and application work.

Should I use .jsonl or .ndjson?

Use the extension required by your tools or published contract. Both commonly mean line-delimited JSON, but media-type conventions differ.

Can a JSONL record contain an array or string?

Yes. Each line may contain any valid JSON value, although many systems standardize on one object shape per line.

How do I migrate an API from JSON to JSONL?

Publish a new media type and contract, define line and error behavior, add record IDs and schema versions, then support both formats during migration.

Decision checklist

  • Choose JSON when the consumer expects one complete document.
  • Choose JSONL when records are independent and should stream, append, or flow through line-based tools.
  • Confirm UTF-8, line endings, blank-line, malformed-record, and media-type rules.
  • Design IDs, schema versions, retries, ordering, and durability separately from the serialization format.