JSONL:我们竟然忽略了这种格式的存在
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
JSON几乎无处不在。 只要需要序列化结构化数据,我们最先想到的通常就是JSON。API响应里有它,配置文件里有它,导出的业务数据离不开它,保存在本地的文件也经常使用它。 而且,JSON语法并不复杂。作为开发者,我们每天都在接触它,似乎早已摸清了它的全部用法。 真的如此吗? 前段时间,我听一档播客时,主持人无意中说了一句话,大意是: “我们使用JSONL在本地保存记录。” 停顿了一下,他又特意补充: “注意,是JSONL,不是JSON。” 显然,在他看来,这两个格式之间的区别非常重要。 其实,我大约一年前就听说过JSONL,只是当时没有深入了解,很快便把它忘在了脑后。再次听到这个名字时,我隐约觉得熟悉,却说不清它与普通JSON究竟有什么不同,更不知道开发者为什么需要关心它。 直到真正开始研究,我才意识到:我们口中的“JSON”,并不是一个孤立的格式。围绕JSON,早已形成了一整个衍生生态。过去这些年里,人们不断扩展或调整基础规范,用来解决原始JSON并未针对的问题。 例如,JSON5允许使用注释和尾随逗号,更适合由人手动编辑的配置文件;JSONC也就是“带注释的JSON”,被VS Code等工具广泛采用;BSON是Binary JSON,即二进制JSON,支撑着MongoDB的内部数据存储;EJSON代表Extended JSON,它补充了更多数据类型;GeoJSON则专门用于表示地理空间数据。 除此之外,大概还有十几种类似格式没有被列出来。 不过,这些衍生格式大多服务于特定领域。GeoJSON面向地图应用,BSON主要用于数据库内部,JSON5则适合需要注释和宽松语法的配置文件。它们解决的,都是具体场景下的具体问题。 JSONL有所不同。 JSONL的全称是“JSON Lines”,它解决的是适用范围更广的一类难题:流式处理与内存占用。 它允许程序逐行处理数据,不必先把整个文件加载进内存。如果数据量不断增长、通过数据流持续到达,或者文件大到足以耗尽可用内存,JSONL就能非常自然地应对。 它没有修改JSON对象本身的语法,只是改变了多个JSON对象在文件中的组织方式。 真正的区别,只有一行从技术层面看,JSON与JSONL的差异并不复杂。 JSON只有一个根结构。 整个数据集必须是一个对象或一个数组。假设文件里存在一千条记录,那么,这一千条数据通常都会被包裹在同一个JSON数组中。 使用许多常见的立即加载型或缓冲型解析器时,程序需要先把完整内容读入内存,才能解析并使用这些数据。 JSONL则由多个相互独立的JSON对象组成,每行一个。 文件里的每一行,都是一条完整、有效且能够单独解析的JSON记录。一千条数据,就是一千行;各行彼此独立,不需要共享外层数组。 实际效果如下: 看上去,不过是增加了几个换行符。 然而,正是这些换行符,彻底改变了程序处理数据的方式。 标准化,才是JSONL真正的优势看到这里,有人可能会问: “我们自己把JSON对象逐行写入文件,再编写一个按换行符拆分内容的解析器,不也能获得相同效果吗?” 从技术上来说,确实可以。 可是,JSONL真正重要的地方,不只是“每行放一个JSON对象”,而在于它已经是一种被广泛识别的标准格式。 使用JSONL,意味着我们没有在项目中发明一套私有规则,而是在采用现有生态已经支持的解决方案。 Apache Spark和Hadoop可以直接高效处理JSONL文件;Python的pandas只需要调用一次函数,并设置 开发者不必重新编写解析器,也不用亲自处理流式读取、错误恢复和各种边界情况。那些经过大量真实项目验证的工具,已经替我们完成了这些工作。 如果坚持使用自定义格式,接下来需要维护的东西会越来越多:
这些工作不会为业务创造更多价值,却会持续增加维护成本。 当另一名开发者看到扩展名为 格式本身就在传递信息。 相反,如果文件采用的是团队自创格式——哪怕规则只是“使用换行符分隔JSON对象”——也会产生额外的文档负担和理解成本。 标准化带来的价值还不止于文件读取。 许多数据库导入工具都能识别JSONL;ETL工具通常提供原生的JSONL读取器; 选择成熟标准,而不是重复造轮子,这些能力几乎都可以直接获得。 JSONL为什么重要:内存与流式处理使用普通JSON文件和缓冲型解析器时,程序一般需要经历以下过程: 先读取整个文件,再解析完整结构,接着在内存中建立全部对象树,最后才能真正处理数据。 JSONL的流程却完全不同: 读取一行,解析这一行,处理这一条记录,然后继续读取下一行。 循环往复,直到文件结束。 假设有一个50MB的普通JSON文件。使用缓冲型解析器时,程序至少需要为原始数据准备约50MB内存;由于解析后还要构建对象结构,实际占用通常会更高。 如果同样的数据以JSONL保存,程序只需要容纳当前正在处理的一行。换句话说,内存需求主要取决于最大单条记录的大小,而不是整个文件的体积。 通常情况下,一条记录显然要比完整文件小得多。 当然,JSON并非完全无法进行流式解析。 例如,Python生态中有 不过,流式JSON解析器与JSONL解决的是两类相互补充的问题。 流式解析器能够保留复杂的数据层次,同时增量读取内容;JSONL则要求把数据整理为“一行一条记录”的形式,用结构上的简化换取处理方式上的简单。 换句话说:
JSONL的一项明显优势在于,它只依赖任何编程语言都具备的基础文件操作。 程序只需要逐行读取,再把当前行交给普通JSON解析器即可。无需引入专门的流式解析库,也不用实现复杂的状态管理。 另一个好处,是程序可以在数据到达时立即开始处理。 如果我们正在持续读取日志文件,或者消费一个已经采用JSONL格式的数据流,那么,每收到一条记录,就能立刻解析和执行相应逻辑。 程序不必等待数据流彻底结束,更不需要苦等最后那个JSON数组闭合符号出现。 播客中的主持人,正是使用JSONL在本地保存应用日志。 仔细想想,这个选择再合理不过:日志只会不断追加,文件体积可能极其庞大,而且经常需要增量处理。 JSONL天然适合这种场景。 JSONL最适合用在哪里JSON依然有大量合理用途。不过,在下面这些场景中,确实值得优先考虑JSONL。 日志文件与事件流日志中的每条记录通常相互独立,因此非常适合使用JSONL。 Apache访问日志、应用遥测数据、错误追踪记录,以及其他会随时间持续积累的数据,都可以逐条写入文件,再按需读取,无须一次加载全部内容。 Elasticsearch、Logstash等工具也能通过标准配置接收JSONL数据。 对于日志系统而言,JSONL还有一个非常实际的优势:追加新记录十分简单。 只需要在文件末尾写入新的一行,不必读取并修改整个数组,也不用担心重新生成庞大的根结构。 机器学习流水线机器学习训练数据往往规模巨大,不适合一次性放入内存。 采用JSONL后,训练程序可以逐条或分批读取数据,再将当前批次送入训练循环。处理完成后释放相关内存,然后继续加载下一批。 Hugging Face的 原因并不神秘:逐行记录让增量读取变得非常直接。 当数据集包含数百万甚至数千万条样本时,这种简单结构会带来实实在在的便利。 消息队列Kafka等消息队列也适合处理类似JSONL的独立消息负载。 每条消息都是完整记录,消费者可以分别解析和处理,不需要共同维护一个庞大的数据结构。 由于消息之间没有外层数组依赖,系统也更容易进行并行消费,把不同记录分配给多个消费者处理。 简单的格式,反而让工作拆分与分发更加容易。 大数据处理Apache Spark和Hadoop等大数据框架能够很好地处理JSONL。 框架可以把文件分配给不同节点,再并行处理各个部分。 从理论上讲,JSON数组和JSONL文件都能按照字节偏移进行拆分。然而,如果直接切割JSON数组,系统必须追踪方括号、对象边界以及嵌套关系,才能保证切割后的内容仍然有效。 JSONL就简单得多。 只要找到换行符,便能较为安全地确定记录边界。即使采用最朴素的按行拆分策略,通常也能正确分配数据。 这正是JSONL在分布式处理中的优势:它不是提供了JSON无法实现的能力,而是让原本复杂的操作变得更容易、更可靠。 ETL数据流水线ETL指的是数据的提取、转换和加载。 当数据需要在多个系统之间流动时,JSONL的标准化结构同样能够发挥作用。 程序可以轻松追加新记录,也能只处理文件中的某个部分,不必先构造完整数据集。与此同时,多数ETL工具都能识别这种格式,并提供经过优化的读取器。 团队不需要维护额外适配代码,也不必为每个系统设计不同的数据交换规则。 流式API越来越多的API开始采用增量方式向客户端发送结果。 以OpenAI的流式API为例,它使用Server-Sent Events,也就是SSE格式。每个事件中可以包含一个JSON对象,客户端收到数据后便能立即处理,而不必等待完整响应全部生成。 严格来说,SSE并不等同于JSONL,但两者背后的思路非常接近: 把完整结果拆成可以独立处理的小记录,并在它们到达时立即消费。 因此,只要面对的是持续到达的大量数据,或者需要增量处理的内容,JSONL往往都能体现优势。 更重要的是,相关生态已经存在,我们不必从零开始搭建所有工具。 哪些情况继续使用JSON更合适JSONL并不是JSON的替代品。 在很多场景中,传统JSON仍然是更自然、更简单的选择。 REST API与浏览器应用REST API和浏览器应用已经围绕JSON形成了非常成熟的生态惯例。 几乎所有HTTP客户端与服务端框架都内置了JSON支持。传统REST接口通常返回JSON数组,或者返回包含分页信息的JSON对象。 JavaScript本身还提供了 JSONL当然也能用于这些场景,但需要增加一层逐行拆分和解析逻辑。虽然实现并不困难,却没有必要为了使用新格式而对抗成熟惯例。 如果现有方式已经简单、可靠,就没有必要强行改变。 配置文件配置文件通常体积较小,而且程序启动时往往需要一次性读取全部内容。 与此同时,配置项天然具有层级关系。例如,数据库配置下面可能包含地址、端口、用户名和连接池参数;日志配置又会包含级别、输出位置以及格式选项。 JSON的嵌套结构非常适合描述这种层级信息。 我们原本就希望把整份配置加载进内存,因此,JSON的单一根结构反而更加自然。 文档数据库MongoDB等文档数据库采用JSON风格的数据结构,目的就是支持内容丰富、层次复杂的文档。 一个文档内部可以包含对象、数组以及多层嵌套关系,从而表达复杂的数据联系。 在这种情况下,结构本身就是重点。将文档强行拆成一行一条的扁平记录,反而可能破坏其原有表达能力。 中小规模数据集如果整个数据集只有几MB,而且程序本来就准备一次性加载全部内容,那么,JSONL带来的内存优势不会特别明显。 此时,JSON的单一结构更容易读取、修改和传递。 为了标准化而增加的逐行处理逻辑,也未必能换来足够收益。 因此,选择JSON还是JSONL,最终可以归结为一个简单判断: 如果数据规模有明确上限,能够轻松放入内存,而且需要作为一个整体处理,就使用JSON。 如果数据没有固定上限,体积非常庞大,或者希望逐条增量处理,就应该考虑JSONL。 关键不在于哪个格式更新,而在于数据将以什么方式产生、增长和被消费。 那么,NDJSON又是什么熟悉JSON各种衍生格式的人,可能还会提到NDJSON。 NDJSON的全称是Newline-Delimited JSON,也就是“以换行符分隔的JSON”。 它与JSONL一样,规定文件中的每一行都应该是一条完整的JSON对象。 那么,两者究竟有什么区别? 在大多数实际场景中,区别主要体现在文件扩展名和命名习惯上。 文件名以 除此之外,两者表达的核心思想基本相同:每行一条独立JSON记录。 因此,不必在名称上纠结太久。真正重要的是团队保持一致,并让使用的工具能够正确识别对应格式。 为什么JSONL很少被重点讨论我一直不太确定,为什么人们很少专门谈论JSONL。 也许关于它的讨论其实一直存在,只是我恰好错过了。又或许,真正的原因更加简单:JSONL实在太朴素了。 它没有需要花几周学习的新框架,没有等待加入的庞大社区,也很少有人在技术大会上把它包装成“革命性的下一代方案”。 它只是一种格式。 一行,一个JSON对象。 没了。 可恰恰是这种不引人注目的工具,往往最实用。它安静地完成任务,现有生态也已经提供了完善支持,开发者根本不用反复思考底层细节。 阅读原文:点击这里 该文章在 2026/9/18 11:16:58 编辑过 |
关键字查询
相关文章
正在查询... |