前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你所不知道的ndJSON:序列化与管道流

你所不知道的ndJSON:序列化与管道流

作者头像
Jean
发布2019-09-16 15:18:31
7K0
发布2019-09-16 15:18:31
举报
文章被收录于专栏:Web行业观察

一直以为对JSON所有的语法都了如指掌,毕竟json的标准用一只手都数的过来,直到我发现了一个叫ndJSON的标准,简单说,以下2种语法都是合法的:

图一:json格式

图二:ndjson格式

其中图一是常见的json格式,而且整个json对象是一个列表:元素由逗号分隔,再由方括号闭合。图二则是一种称为ndJSON的格式,由换行符(0x0A)分隔每个json对象,最外面也没有闭合字符对。ndjson的mime类型是application/x-ndjson。

需要注意的是,图一和图二并不等同,就是说,图一和图二不仅使用了不同的序列化格式,数据所表达的含义也是不同的。图一只表达了一个对象:一个列表,图二则表达了3个对象:3个“data”字典。这个区别是json和ndjson的本质区别。

NDJSON(ndjson.org)

ndjson(New-line Delimited JSON)是一个比较新的标准,本身超简单,就是一个.ndjson文件中,每行都是一个传统json对象,当然每个json对象中要去掉原本用于格式化的换行符,而json的string中本身就不允许出现换行符(取而代之的是\n),所以ndjson在语法上基本不会出现歧义。但现在问题来了,ndjson有什么用?

JSON流问题(https://en.wikipedia.org/wiki/JSON_streaming)

新的标准总是来自于新的需求。ndjson的出现起源于json流问题。当时,我在设计一个方法用于将mongodb数据库的一张表备份到一个文件中,由于涉及到3个端的数据传输而没有对数据做整体处理的需求,就得使用管道流了。

其实流的概念非常简单,所有的数据传输都是流,都需要把大的数据分割成若干小份然后依次传输,只不过大多情况下传输都是通过底下的api自动完成的,我们感受不到“分割”的过程,也很难感受到“管道传输”的过程。正是这种底层的屏蔽造成了我们的无知,当要我们亲自设计管道的时候就嗝屁了。

在上面这个跨3端管道传输数据流的任务中,需要一边序列化一边走管道,最合适的做法就是将整张表格分割成一个个json对象(无论是sql还是mongo,表中的每一行都可以看成一个json对象),然后通过主机管道流向文件系统。这里出现了一个问题,数据流的最终存在形式是什么?是一个json文件吗?不可能,因为json文件只能表示一个json对象,而数据库表中有若干个对象。那给mysql表中的每一行保存一份json文件?好像也不合适。

HACK JSON

勉强的方法是使用一个json文件存放一份超长的json列表来收纳每一行数据。之所以勉强是因为构造一个json列表需要一些hack技巧:一开始需要写一个‘[’,中间每个json对象之间需要写‘,’,传输完成后又需要一个‘]’,所以我的代码是这样的:

代码语言:javascript
复制
fsWriter.write('[')
mongoReader.on('data', rowObj => {
    fsWriter.write(JSON.stringify(rowObj));
    fsWriter.write(',');
});
mongoReader.on('close', () => {
    // 由于json不允许在最后一个列表元素后面加逗号,hack一个空字典
    fsWriter.write('{}]');
    fsWriter.end();
});

只能说,hack一时爽,一直hack一直爽,天天hack火葬场。通过hack来达到目的是有后遗症的,容易给你带来一堆麻烦事。假如我想在json文件最后插入一条记录或者读取一条记录怎么办?json是作为一个整体来编译处理的,想要读取其中的某一部分也得先编译整个json对象。这是json设计上的一个缺陷,即整体无法直接分割,当然如果你想hack json的话我也不拦你,只是如果想要实现一个通用的方法就得重新设计json流的格式了。带着这个疑问,我想起了程序员3大错觉之一的“我超越了标准库”,于是在维基百科上查了一下原来真的有json流格式。

如图,维基百科介绍了4种不同的json流解决方案,其中第一种就是本文一开始讲到的ndjson,即使用换行符分割的json,由于换行符的特殊性,不会出现歧义:

代码语言:javascript
复制
{"some":"thing\n"}
{"may":{"include":"nested","objects":["and","arrays\n"]}}

ndjson和第二种解决方案比较相似,第二种是通过2个更特殊的控制字符来分割(确切的说是包裹)每一个独立对象,这两个字符是记录分隔符<RS>和行尾反馈符<LF>,这种解决方案利用这2个我前所未闻的控制字符来包裹每个json对象,颇有点超文本标记语言的感觉:

代码语言:javascript
复制
<RS>{"some":"thing"}<LF>
<RS>{
  "may": {
    "include": "nested",
    "objects": [
      "and",
      "arrays"
    ]
  }
}<LF>

图中第三种和第四种方案我就不推荐啦,第三种是不要分隔符,前后2个对象直接相连:

代码语言:javascript
复制
{"some":"thing\n"}{"may":{"include":"nested","objects":["and","arrays"]}}

第四种模仿二进制格式,将对象长度写在前缀里:

代码语言:javascript
复制
18{"some":"thing\n"}55{"may":{"include":"nested","objects":["and","arrays"]}}

这两种方案不仅长相丑陋,而且还容易引起歧义,强烈不推荐使用,而且4种方案中也只有第一种的ndjson实现了标准化,它也是最常用的。当然,这4种都是文本格式的流解决方案,在二进制流领域中问题就简单得多了,比如message pack对象的长度就写在前缀中,对象之后可以直接拼接下一个对象而不会出现任何歧义,就像刚刚的方案三一样。

最后总结一下ndjson对json的性能提升:ndjson使整个文件“流化”,或者说把整个文件分割成许多份,这样避免了整体的束缚,支持局部处理,变得更灵活更快,从而实现了序列化和流传输的同时进行。

参考链接

https://jimmy.blog.csdn.net/article/details/90678160

https://medium.com/@kandros/newline-delimited-json-is-awesome-8f6259ed4b4b

http://ndjson.org/

https://github.com/ndjson/ndjson-spec

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebHub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档