Data Formats

JSONL vs JSON — Key Differences and When to Use Each Format

JSON is the universal data format for APIs and configs. JSONL (JSON Lines) is the streaming-friendly alternative for logs, big data, and machine learning. Here's how they compare and when to use each.

What is JSON?

JSON (JavaScript Object Notation) stores structured data as a single root value — typically an object or array. The entire document must be valid and loaded into memory to parse.

{
  "users": [
    {"id": 1, "name": "Alice", "role": "engineer"},
    {"id": 2, "name": "Bob", "role": "designer"},
    {"id": 3, "name": "Carol", "role": "manager"}
  ]
}

JSON is the standard for REST APIs, configuration files (package.json, tsconfig.json), and data exchange between services. Almost every language has built-in JSON support.

What is JSONL (JSON Lines)?

JSONL stores each record as a separate, valid JSON object on its own line. No wrapping array, no commas between objects, no trailing commas. Each line is independent.

{"id": 1, "name": "Alice", "role": "engineer"}
{"id": 2, "name": "Bob", "role": "designer"}
{"id": 3, "name": "Carol", "role": "manager"}

JSONL is also called NDJSON (Newline-Delimited JSON). The two names refer to the same format. JSONL uses .jsonl extension; NDJSON uses .ndjson.

Key Differences

FeatureJSONJSONL
StructureSingle root value (object/array)One JSON object per line
ParsingParse entire file at onceParse line by line
Memory usageO(n) — entire file in memoryO(1) per record — constant
StreamingNot supported nativelyBuilt-in — read line by line
Appending dataMust rewrite entire fileJust append a new line
Error recoveryOne error breaks entire parseSkip bad lines, continue
Parallel processingDifficult (nested structure)Easy (split by lines)
File extension.json.jsonl or .ndjson
Best forAPIs, configs, small datasetsLogs, big data, ML, streaming

When to Use JSON

API request and response payloadsREST APIs and GraphQL return a single JSON object per request.
Configuration filespackage.json, tsconfig.json, .eslintrc — small, human-edited, loaded once.
Small to medium datasetsData that fits comfortably in memory (under ~100MB).
Nested, hierarchical dataParent-child relationships, tree structures, and deeply nested objects.
Single-record responsesFetching one user, one product, one order — a single JSON object.

When to Use JSONL

Log filesEach log entry is a JSON object on its own line. Easy to tail, grep, and stream.
Machine learning datasetsTraining data, embeddings, and feature vectors — often millions of records.
Data pipelines and ETLKafka messages, event streams, and data warehouse imports.
Large datasetsMillions of records that cannot fit in memory. Stream line by line with constant memory.
Append-only dataAudit logs, event stores, time-series data — just append a new line.
Parallel processingSplit file across workers by line count. Each worker processes its chunk independently.
Server-Sent Events (SSE)Streaming JSON data to browsers over a persistent connection.

Converting Between Formats

JSON Array to JSONL

// Convert a JSON array to JSONL
const data = require('./data.json');  // { "users": [...] }
const jsonl = data.users
  .map(user => JSON.stringify(user))
  .join('\n');

require('fs').writeFileSync('data.jsonl', jsonl);

JSONL to JSON Array

// Convert JSONL back to a JSON array
const fs = require('fs');
const lines = fs.readFileSync('data.jsonl', 'utf8')
  .trim()
  .split('\n');
const users = lines.map(line => JSON.parse(line));
fs.writeFileSync('data.json', JSON.stringify({ users }, null, 2));

Streaming JSONL (Large Files)

// Stream JSONL line by line — constant memory
const readline = require('readline');
const fs = require('fs');

const rl = readline.createInterface({
  input: fs.createReadStream('large-data.jsonl'),
});

let count = 0;
rl.on('line', (line) => {
  try {
    const record = JSON.parse(line);
    // Process each record
    count++;
  } catch {
    // Skip malformed lines
    console.error('Bad line:', line.slice(0, 100));
  }
});

rl.on('close', () => {
  console.log(`Processed ${count} records`);
});

Frequently Asked Questions

What is the main difference between JSON and JSONL?

JSON stores data as a single root value (object or array) that must be fully loaded into memory to parse. JSONL stores each record as a separate JSON object on its own line, allowing line-by-line streaming with constant memory usage. JSON is best for APIs and configs; JSONL is best for logs, big data, and streaming.

Is JSONL the same as NDJSON?

For practical purposes, yes. Both use newline-delimited JSON objects with the same rules: one JSON object per line, UTF-8 encoding, newline as delimiter. JSONL (jsonlines.org) uses the .jsonl extension; NDJSON (ndjson.org) uses .ndjson. Most tools support both formats interchangeably.

Can I use JSONL for API responses?

JSONL is not standard for REST API responses — use JSON. However, JSONL is the right choice for streaming APIs (Server-Sent Events, WebSocket event streams, Kafka consumers) where you send a continuous stream of records. Some APIs offer both: JSON for single requests, JSONL for bulk/streaming endpoints.

How do I validate JSONL files?

Each line must be a valid JSON object. Validate by parsing each line independently: cat data.jsonl | python3 -c "import sys,json; [json.loads(l) for l in sys.stdin]". Our JSONL Formatter tool validates and formats JSONL files, highlighting any malformed lines.

Key Takeaways

  • Use JSON for APIs, configs, and small datasets that fit in memory.
  • Use JSONL for logs, big data, streaming, and append-only data.
  • JSONL enables line-by-line streaming with O(1) memory per record.
  • JSONL and NDJSON are the same format with different names.
  • Converting between formats is straightforward — JSON.stringify() each record and join with newlines.

Related Resources