JSON vs JSONL vs NDJSON: What's the Difference?

8 min readData Formats

Introduction

If you work with data, you know JSON. But you may have also encountered JSONL (JSON Lines) or NDJSON (Newline-Delimited JSON) and wondered: are they the same thing? What's the difference, and when should you use each format? This guide breaks down the key differences, trade-offs, and real-world use cases for each format so you can make the right choice for your project.

What is JSON?

JSON (JavaScript Object Notation) is a text-based data format that stores structured data as key-value pairs, arrays, and nested objects. A JSON file contains a single root value - typically an object or array.

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

JSON is the standard for APIs, configuration files, and data exchange. Almost every programming language has built-in JSON parsing. But it has a limitation: the entire document must be loaded into memory to parse it.

What is JSONL (JSON Lines)?

JSONL (also called NDJSON - Newline-Delimited JSON) is a format where each line in the file is a separate, valid JSON object. There are no commas between objects, no wrapping array, and 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 not a replacement for JSON - it's a different format for different use cases. It's particularly popular in data engineering, logging, and machine learning pipelines.

JSONL vs NDJSON: Same Thing?

For practical purposes, JSONL and NDJSON are the same format. Both use newline-delimited JSON objects. The difference is purely naming:

  • JSONL (jsonlines.org) - the original specification, uses .jsonl file extension
  • NDJSON (ndjson.org) - a community specification, uses .ndjson file extension

Both follow the same rules: one JSON object per line, UTF-8 encoding, newline (\n) as delimiter. Most tools that support one also support the other.

Side-by-Side Comparison

FeatureJSONJSONL / NDJSON
StructureSingle root value (object/array)One JSON object per line
StreamingMust load entire fileLine-by-line streaming
Memory usageO(n) - entire file in memoryO(1) per record - constant
Append 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
API responsesStandard formatServer-Sent Events, streaming
Human readabilityHigh (with formatting)Medium (one object per line)

When to Use JSON

  • API request/response payloads - REST APIs, GraphQL responses
  • Configuration files - package.json, tsconfig.json, .eslintrc
  • Small to medium datasets - data that fits comfortably in memory
  • Nested, hierarchical data - when you need parent-child relationships
  • Single-record responses - fetching one user, one product, etc.

When to Use JSONL / NDJSON

  • Log files - each log entry is a JSON object on its own line
  • Machine learning datasets - training data, embeddings, feature vectors
  • Data pipelines - ETL processes, Kafka messages, event streams
  • Large datasets - millions of records that can't fit in memory
  • Append-only data - audit logs, event stores, time-series data
  • Parallel processing - split file across workers, each processes a chunk of lines
  • Server-Sent Events (SSE) - streaming JSON data to browsers

Code Examples

Reading JSON in Node.js

// Must load entire file into memory
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('data.json', 'utf8'));
data.users.forEach(user => console.log(user.name));

Reading JSONL in Node.js (Streaming)

// Stream line by line - constant memory usage
const readline = require('readline');
const fs = require('fs');

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

rl.on('line', (line) => {
  const record = JSON.parse(line);
  console.log(record.name);
});

Converting JSON to JSONL

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

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

Converting JSONL to JSON

// 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));

Performance Comparison

For a dataset of 1 million user records (~200 bytes each, ~200MB total):

OperationJSONJSONL
Parse entire file~2s, 400MB RAM~1.5s, ~1MB RAM (streaming)
Append 1 recordRewrite entire fileAppend 1 line (O(1))
Process in parallelComplex splittingSplit by line count
Recover from corruptionEntire file lostOnly bad lines lost

Key Takeaways

  • Use JSON for APIs, configs, and small datasets that fit in memory
  • Use JSONL/NDJSON for logs, large datasets, streaming, and append-only data
  • JSONL and NDJSON are effectively the same format with different names
  • JSONL's streaming capability makes it ideal for data pipelines and big data processing
  • Converting between formats is straightforward with any programming language

Related Tools