JSON Formatter Hub

Free online JSON formatter, validator, and developer tools.

JSON Lines (JSONL): The Format Built for Logs, Streams, and Big Data

A standard JSON array requires the whole document to be valid before you can safely read any of it โ€” one malformed record at the end, or a process that dies mid-write, and the entire file becomes unparseable. JSON Lines (also written JSONL, or newline-delimited JSON / NDJSON) fixes this by dropping the wrapping array entirely: each line is its own complete, independently parseable JSON value.

The Format

A JSONL file is plain text where every line is a self-contained JSON value, almost always an object, separated by a single newline character (\n). There is no wrapping [ ], and no commas between records:

{"timestamp": "2026-07-12T09:00:00Z", "level": "info", "message": "Server started"}
{"timestamp": "2026-07-12T09:00:03Z", "level": "warn", "message": "High memory usage", "pct": 87}
{"timestamp": "2026-07-12T09:00:07Z", "level": "error", "message": "Connection timeout", "host": "db-2"}

Each line above is independently valid JSON. That single property โ€” line-level independence โ€” is what standard JSON arrays lack and what makes JSONL suited to a different class of problem.

Why Wrap-Array JSON Breaks Down at Scale

Compare the same data as a standard JSON array:

[
  {"timestamp": "2026-07-12T09:00:00Z", "level": "info", "message": "Server started"},
  {"timestamp": "2026-07-12T09:00:03Z", "level": "warn", "message": "High memory usage", "pct": 87},
  {"timestamp": "2026-07-12T09:00:07Z", "level": "error", "message": "Connection timeout", "host": "db-2"}
]

Three practical problems emerge once this file grows to gigabytes or is written incrementally:

JSONL solves all three by making every record independently addressable and appendable.

Where JSONL Is the Right Tool

Reading and Writing JSONL in Python

No special library is required for basic use โ€” plain line iteration plus json.loads/json.dumps is often enough:

import json

# Reading โ€” stream line by line, never load the whole file into memory
def read_jsonl(path):
    with open(path, 'r') as f:
        for line in f:
            line = line.strip()
            if line:
                yield json.loads(line)

for record in read_jsonl('logs.jsonl'):
    print(record['level'], record['message'])

# Writing โ€” append one record at a time
def append_jsonl(path, record):
    with open(path, 'a') as f:
        f.write(json.dumps(record) + '\n')

append_jsonl('logs.jsonl', {"timestamp": "2026-07-12T09:00:10Z", "level": "info", "message": "Request handled"})

For larger workloads, the jsonlines package wraps this pattern with a slightly cleaner API and built-in error handling for malformed lines:

pip install jsonlines
import jsonlines

with jsonlines.open('logs.jsonl') as reader:
    for obj in reader:
        print(obj)

with jsonlines.open('logs.jsonl', mode='a') as writer:
    writer.write({"timestamp": "2026-07-12T09:00:12Z", "level": "info", "message": "Done"})

Reading and Writing JSONL in Node.js

For small-to-medium files, splitting on newlines is sufficient:

const fs = require('fs');

// Reading
const lines = fs.readFileSync('logs.jsonl', 'utf8').split('\n').filter(Boolean);
const records = lines.map(line => JSON.parse(line));

// Writing / appending
function appendJsonl(path, record) {
  fs.appendFileSync(path, JSON.stringify(record) + '\n');
}
appendJsonl('logs.jsonl', { timestamp: new Date().toISOString(), level: 'info', message: 'Request handled' });

For large files where you cannot hold the whole thing in memory, stream it line-by-line with Node's readline module:

const fs = require('fs');
const readline = require('readline');

async function processLargeJsonl(path) {
  const rl = readline.createInterface({
    input: fs.createReadStream(path),
    crlfDelay: Infinity,
  });

  for await (const line of rl) {
    if (!line.trim()) continue;
    const record = JSON.parse(line);
    // process record here, one at a time โ€” constant memory usage
  }
}

Command-Line Tools That Understand JSONL

jq processes JSONL natively โ€” pass one record through the filter for each line:

# Filter to only error-level log lines
cat logs.jsonl | jq 'select(.level == "error")'

# Extract just the message field from every line
cat logs.jsonl | jq -r '.message'

# Convert a JSON array file into JSONL
jq -c '.[]' data.json > data.jsonl

# Convert JSONL back into a JSON array
jq -s '.' data.jsonl > data.json

Standard Unix tools also work directly because the format is line-oriented: wc -l logs.jsonl counts records, tail -f logs.jsonl tails a live-growing log, and split -l 100000 logs.jsonl chunk_ splits a huge file into fixed-size chunks without ever parsing a single record.

JSONL vs a JSON Array: When to Use Each

Use a standard JSON array when the data is a single cohesive document that's always read and written as a whole โ€” an API response, a config file, a small dataset that comfortably fits in memory. Use JSONL when records are generated incrementally over time, need to be processed one at a time without loading everything into memory, or need to survive partial writes gracefully โ€” logs, event streams, and large training datasets are the classic cases. If you're unsure, ask whether the file is ever appended to after creation; if yes, JSONL is almost always the better fit.

Summary

JSON Lines trades the single-document guarantees of a JSON array for line-level independence: constant-memory streaming reads, safe appends, and compatibility with line-oriented Unix tooling. It's not a replacement for JSON arrays in general โ€” it's the right specialized format specifically for logs, streams, and large sequential datasets where records arrive or need to be processed one at a time.

Need to inspect a single record from a JSONL file? Paste just that line into our free JSON Formatter to pretty-print and validate it.