Skip to content
Tools

What Is NDJSON (JSON Lines)? Format, Uses and How to Read It

· 4 min read

NDJSON (newline-delimited JSON), also called JSON Lines or JSONL, is a text format where each line is one complete JSON value, usually an object, and lines are separated by \n. Because every line parses on its own, files can be streamed, appended to and split without loading everything into memory, which is why logs, data exports and bulk APIs use it.

What it looks like

events.ndjson
{"ts":"2026-10-30T09:00:00Z","level":"info","msg":"started"}
{"ts":"2026-10-30T09:00:02Z","level":"warn","msg":"slow query","ms":812}
{"ts":"2026-10-30T09:00:05Z","level":"error","msg":"timeout","retry":true}

That file is not valid JSON as a whole: there is no enclosing array and no commas between records. A normal JSON parser fails on line 2. The equivalent JSON array would be [{...},{...},{...}].

The rules

  • Each line is a valid JSON value. Objects are typical; the JSON Lines description allows any value.
  • Lines are separated by \n. A \r\n ending also works, because \r is whitespace a JSON parser ignores.
  • No pretty printing: a record cannot span lines. Line breaks inside strings must be escaped as \n, which JSON.stringify and json.dumps do automatically.
  • UTF-8 encoding.
  • A trailing newline after the last record is recommended. Readers should skip blank lines rather than fail on them.

NDJSON, JSON Lines, JSONL: same thing?

In practice, yes. NDJSON and JSON Lines were published as separate informal specs with near-identical rules, and the names are used interchangeably. File extensions are .ndjson, .jsonl or sometimes .ldjson. A related standard, JSON Text Sequences (RFC 7464, media type application/json-seq), prefixes each record with an ASCII record separator character; it is rarer and not interchangeable without conversion.

JSON array vs NDJSON

JSON arrayNDJSON
ParseWhole document at onceOne line at a time
Memory for big filesGrows with file sizeRoughly one record
Append a recordRewrite the closing ]Write one line
A corrupt recordWhole file failsOnly that line fails
Split / parallel processingNeeds a streaming parsersplit -l, head, grep work
Human readabilityCan be pretty printedOne dense line per record
Typical usesAPI responses, configLogs, exports, ML datasets, bulk APIs

You will meet it in structured logs, database and analytics exports, the Elasticsearch _bulk API, OpenAI batch and fine-tuning files (.jsonl), and streaming HTTP responses that emit one JSON object per line.

Reading NDJSON

Python

import json

with open("events.ndjson", encoding="utf-8") as f:
    for n, line in enumerate(f, 1):
        if not line.strip():
            continue
        try:
            record = json.loads(line)
        except json.JSONDecodeError as e:
            print(f"line {n}: {e}")
            continue
        print(record["level"], record["msg"])

# pandas: whole file into a DataFrame
import pandas as pd
df = pd.read_json("events.ndjson", lines=True)

Node.js (streaming)

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

const lines = createInterface({ input: createReadStream("events.ndjson"), crlfDelay: Infinity });

for await (const line of lines) {
  if (!line.trim()) continue;
  const record = JSON.parse(line);
  if (record.level === "error") console.log(record.msg);
}

jq

jq -c 'select(.level == "error")' events.ndjson   # filter, keep NDJSON
jq -s '.' events.ndjson > events.json              # NDJSON -> JSON array
jq -c '.[]' events.json > events.ndjson            # JSON array -> NDJSON
wc -l events.ndjson                                # count records

jq reads a stream of JSON values natively, so NDJSON needs no special flag. -s (slurp) collects them into one array, and -c prints each output on one line, which produces NDJSON.

Writing NDJSON

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

Do not pass indent here; pretty printing would split a record over several lines. In JavaScript, records.map((r) => JSON.stringify(r)).join("\n") + "\n" does the same for small data; for large data, write each line to a stream.

Validating and viewing NDJSON in the browser

Switch the input format of the JSON formatter to NDJSON and it validates each line separately, listing errors by line number so one broken record is easy to find. Format and minify keep one record per line, and the table view shows records as rows. For multi-hundred-megabyte exports, the large JSON viewer is built for size, and the JSON to CSV converter turns records into a spreadsheet once you have them as an array.

Log exports often contain tokens, emails and IPs. Everything here runs locally, but before you attach a sample to a bug report, run it through the secret scanner.

FAQ

What is the difference between JSON and NDJSON?

A JSON file holds one value, often an array of records. NDJSON holds one JSON value per line with no enclosing array or commas, so it can be streamed and appended line by line.

Is JSONL the same as NDJSON?

For practical purposes, yes. JSON Lines (.jsonl) and NDJSON (.ndjson) describe the same one-value-per-line format.

How do I convert NDJSON to a JSON array?

Run jq -s '.' file.ndjson, or in Python read each line with json.loads into a list and json.dump the list.

Can NDJSON records be pretty printed?

Not inside the file, because each record must stay on one line. Pretty print a single record for reading, then write it back compact.

What MIME type does NDJSON use?

application/x-ndjson is the most common in practice; application/jsonl is also seen. Neither is an IANA-registered standard type.

More JSON guides