NDJSON 完整指南:换行分隔 JSON 详解
关于换行分隔 JSON (NDJSON) 的全面指南。学习规范、MIME 类型、如何在 Python、Node.js 和命令行中读写 NDJSON、在流式 HTTP API 中使用它,以及了解它与 JSONL 的关系。
最后更新:2026年2月
什么是 NDJSON?
NDJSON 全称 Newline Delimited JSON(换行分隔 JSON)。它是一种基于文本的数据格式,每一行包含一个有效的 JSON 值,由换行符 (\n) 分隔。该格式专为流式处理和处理大型数据集而设计,无需将整个文件加载到内存中。与将所有内容包装在单个数组或对象中的标准 JSON 文件不同,NDJSON 允许您逐条读取、写入和处理记录。
NDJSON 规范托管在 github.com/ndjson/ndjson-spec。它的创建是为了正式化在日志传输、数据管道和 HTTP 流式 API 中已经普遍使用的模式。每一行都是自包含的:如果一行包含无效的 JSON,其他行仍然可以被成功解析。这使得 NDJSON 具有容错性,非常适合应用日志、事件流和增量数据导出等仅追加工作流。
{"id":1,"event":"page_view","url":"/home"}{"id":2,"event":"click","url":"/pricing"}{"id":3,"event":"signup","url":"/register"}
NDJSON 规范
github.com/ndjson/ndjson-spec 上的官方 NDJSON 规范故意保持简洁。它定义了一种在单个文本流中序列化多个 JSON 值的简单约定。核心规则很直接:
规范明确避免向格式添加头信息、元数据或 Schema 信息。这使 NDJSON 尽可能简单,并与标准 Unix 文本处理工具兼容。每一行允许任何有效的 JSON 值,尽管在实践中大多数 NDJSON 文件包含具有一组一致键的 JSON 对象。
- 每一行必须包含恰好一个有效的 JSON 值(对象、数组、字符串、数字、布尔值或 null)。
- 行由换行符 '\n' (U+000A) 分隔。回车符 '\r' (U+000D) 可以出现在 '\n' 之前,但不是必需的。
- 最后一个 JSON 值之后的尾随换行符是允许的但不是必需的。
- 每个 JSON 值内部不得包含未转义的换行符。
由于规则最少,NDJSON 可以从任何具有 JSON 序列化器的编程语言轻松生成。只需序列化每条记录,追加一个换行符,然后写入输出。无需闭合括号、无需尾随逗号、无需包围数组结构。
NDJSON vs JSON vs JSONL
NDJSON、JSON 和 JSONL 各有不同的结构。标准 JSON 编码单个值(通常是数组或对象)。JSONL 和 NDJSON 都是每行存储一个 JSON 值,并且它们在功能上是完全相同的。下表突出了这三种格式之间的主要差异。
| 特性 | JSON | JSONL | NDJSON |
|---|---|---|---|
| 全称 | JavaScript Object Notation | JSON Lines | Newline Delimited JSON |
| 文件扩展名 | .json | .jsonl | .ndjson |
| MIME 类型 | application/json | application/jsonl(非官方) | application/x-ndjson |
| 规范 | RFC 8259(IETF 标准) | jsonlines.org(社区) | github.com/ndjson/ndjson-spec(社区) |
| 行分隔符 | 不适用(单个值) | \n(换行符) | \n(换行符) |
| 尾随换行符 | 不适用 | 推荐 | 可选 |
| 适合流式处理 | 否(必须解析整个文档) | 是(逐行处理) | 是(逐行处理) |
NDJSON MIME 类型:application/x-ndjson
NDJSON 的注册 MIME 类型是 application/x-ndjson。此内容类型用于 HTTP 头中,表示响应体包含换行分隔的 JSON 数据。许多流式 API,包括 GitHub API、Docker Registry API 和 Elasticsearch 批量 API,都使用此 MIME 类型来传递 NDJSON 响应。
Content-Type: application/x-ndjson# Example: curl a streaming APIcurl -H "Accept: application/x-ndjson" https://api.example.com/events/stream# Example: Express.js responseres.setHeader('Content-Type', 'application/x-ndjson');res.write(JSON.stringify(record) + '\n');
一些 API 也接受 application/json-seq (RFC 7464) 或 text/plain 作为替代。然而,application/x-ndjson 是换行分隔 JSON 流最广泛采用的 MIME 类型。在构建流式传输 JSON 记录的新 API 时,请使用 application/x-ndjson 以获得最大兼容性。
读写 NDJSON
在任何支持 JSON 的语言中处理 NDJSON 都很简单。以下是 Python、Node.js 和命令行的实用示例。
Python 内置的 json 模块天然支持 NDJSON。从文件中读取行,使用 json.loads 解析每一行,使用 json.dumps 写入。对于大文件,这种逐行方式使用恒定的内存。
guide-ndjson-complete-guide.ndjsonGuide.readWrite.python.code
在 Node.js 中,使用 readline 模块配合 fs.createReadStream 高效解析 NDJSON 文件。流式处理一次一行,无论文件大小如何,内存使用量都保持较低。
import { createReadStream, writeFileSync } from 'node:fs';import { createInterface } from 'node:readline';// Read NDJSONasync function readNdjson(filePath) {const records = [];const rl = createInterface({input: createReadStream(filePath, 'utf-8'),crlfDelay: Infinity,});for await (const line of rl) {const trimmed = line.trim();if (trimmed) records.push(JSON.parse(trimmed));}return records;}// Write NDJSONconst data = [{ id: 1, event: 'page_view' },{ id: 2, event: 'click' },];const ndjson = data.map(r => JSON.stringify(r)).join('\n') + '\n';writeFileSync('output.ndjson', ndjson, 'utf-8');
jq 命令行工具原生支持 NDJSON 输入。使用 --slurp 标志将所有记录收集到数组中,或不加任何标志单独处理每条记录。
# Print each record (jq reads NDJSON by default)jq '.' data.ndjson# Filter records where event is "click"jq 'select(.event == "click")' data.ndjson# Extract specific fieldsjq {id, url} data.ndjson# Count total recordsjq -s 'length' data.ndjson# Convert NDJSON to a JSON arrayjq -s '.' data.ndjson > data.json# Convert a JSON array back to NDJSONjq -c '.[]' data.json > data.ndjson
NDJSON 在 HTTP 流式 API 中的应用
NDJSON 是传递实时数据的 HTTP 流式 API 的事实标准。当服务器发送 NDJSON 响应时,客户端可以在第一条记录到达时立即开始处理,无需等待整个响应完成。这比返回大型 JSON 数组更快、更节省内存。
使用 NDJSON 流式传输的热门服务包括 Docker Registry(镜像层事件)、Elasticsearch(批量操作)、Apache CouchDB(变更订阅)以及许多现代事件驱动 API。这种模式在 Server-Sent Events 替代方案、实时日志跟踪和 Web 应用程序中的渐进式数据加载中效果很好。
import express from 'express';const app = express();app.get('/api/events/stream', (req, res) => {res.setHeader('Content-Type', 'application/x-ndjson');res.setHeader('Transfer-Encoding', 'chunked');// Simulate streaming eventslet id = 0;const interval = setInterval(() => {id++;const event = {id,type: 'heartbeat',timestamp: new Date().toISOString(),};res.write(JSON.stringify(event) + '\n');if (id >= 100) {clearInterval(interval);res.end();}}, 100);req.on('close', () => clearInterval(interval));});app.listen(3000);
async function* readNdjsonStream(url) {const response = await fetch(url, {headers: { 'Accept': 'application/x-ndjson' },});const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();let buffer = '';while (true) {const { done, value } = await reader.read();if (done) break;buffer += value;const lines = buffer.split('\n');buffer = lines.pop();for (const line of lines) {if (line.trim()) yield JSON.parse(line);}}if (buffer.trim()) yield JSON.parse(buffer);}// Usagefor await (const event of readNdjsonStream('/api/events/stream')) {console.log('Received:', event);}
NDJSON 生态系统工具
越来越多的命令行工具和库原生支持 NDJSON。这些工具让您无需编写自定义代码即可过滤、转换和分析 NDJSON 数据。
jq
必备jq 是最流行的命令行 JSON 处理器。它默认读取 NDJSON(每行一个 JSON 值),支持过滤、映射、分组和重新格式化。使用 jq -c 输出紧凑格式,使用 jq -s 将所有记录收集到数组中。
ndjson-cli
CLIndjson-cli 是一组 Unix 风格的 NDJSON 流操作命令:ndjson-filter、ndjson-map、ndjson-reduce、ndjson-sort 和 ndjson-join。每个命令从 stdin 读取并写入 stdout,使它们可以通过管道组合使用。
ndjson (npm)
Node.jsndjson npm 包为 Node.js 提供流式 NDJSON 解析器和序列化器。它暴露 ndjson.parse() 和 ndjson.stringify() 转换流,可直接集成到 Node.js 流式管道中进行高吞吐量数据处理。
NDJSON 与 JSONL:互操作性
NDJSON 和 JSONL (JSON Lines) 是功能上完全相同的格式。两者都是每行存储一个 JSON 值,由换行符分隔。一个有效的 NDJSON 文件同时也是有效的 JSONL,反之亦然。您可以将 .ndjson 文件重命名为 .jsonl(或反过来),无需更改任何一个字节的内容,读取一种格式的每个工具都会读取另一种。
唯一的差异是表面的:NDJSON 来自 github.com/ndjson/ndjson-spec,使用 .ndjson 扩展名和 application/x-ndjson MIME 类型,而 JSONL 来自 jsonlines.org,使用 .jsonl 扩展名。在实践中,大多数开发者将这两个名称视为同义词。如果您的项目已经使用 JSONL,则无需迁移到 NDJSON;如果一个库说它支持 NDJSON,它也会无需任何更改地处理您的 .jsonl 文件。
试试我们的免费 NDJSON/JSONL 工具
直接在浏览器中处理 NDJSON 和 JSONL 文件。所有处理均在本地完成,您的数据始终保持私密。