说到json,相信没有人会陌生,我们天天都在用。那么,我们来讨论个问题,json有序吗?是谁来决定的呢?如何保持?

  说到底,json是框架还是啥?实际上它只是一个数据格式,一个规范标准,它永远不会限制实现方的任何操作,即不会自行去保证什么顺序性之类的。json的格式仅由写入数据的一方决定其长像如何。而数据读取一方,则按照json的协议标准进行解析,即可理解原数据的含义。json拥有较为丰富的数据格式,所以对当前应用还是比较友好的。

  那么,我们如何处理json的顺序性呢?

1. 保持json有序的思路

  首先,我们要澄清有序性的概念:从某种程度上,我们可以把json看作是一个个的kv组成的数据,从这个层面上来讲,我们可以把有序性定义为json的key保持有序,先假设为字典序吧,那么就说这个json数据是有序的。

  其次,因为json的数据支持嵌套,所以,我们应该需要保持每一层的数据都有序,才是完整有序的。

  ok, 理解完有序的概念,下面我们来看看如何实现有序?

  json本身是不可能保持有序了,所以,当我们自行写入json数据时,只需要按照 abcde... 这种key顺序写入数据,那么得到的最终json就是有序的。

  但我们一般都是使用对象进行程序变换的,所以,就应该要从对象中取出有序的key, 然后序列化为json.

  这里保持有序,至少有两个层面的有序:1. kv形式的key的有序; 2. 列表形式的数据有序; 还有其他可能非常复杂的有序性需求,比如按照某字段有序,倒序。。。

  所以,想保持json有序很简单,保证有序写入就可以了。(貌似等于没有说哦)

2. 保持json有序的应用场景举例

  为什么要保持json有序呢?json相当于kv数据,一般情况下我们是不需要保证有序的,但有些特殊情况下也许有用。比如我有两份json数据,我想比较它们是否是相等的时候!

  比如第一份数据是 {"a":1, "b":2}, 第二份数据是 {"b":2, "a":1}, 那么你说这两份数据是否是相等的呢?相等或不相等依据是啥?

  如果对于固定数据结构的json, 那么也许我们可以直接取出每个key的值,然后进行比较,全部相等则相等成立,否则不相等。

  但对于json本身就是各种不确定的数据组成,如果我们限制死必须取某些key, 那么这个通用性就很差了。所以,我们要想比较两个json是否相等,还应该要有另外的依据。

  另外,当我们将有序json写入文件之后,当key的数据非常多时,有序实际上可以辅助我们快速找到对应的key所在的位置。这是有序性带来的好处,快速查找!

  比如下面的例子,对比两个结果集是否相等,你觉得结果当如何呢?

    @Test
public void testJsonObjectOrder() {
String res1, res2;
List<Map<String, Object>> nList;
Map<String, Object> data = new HashMap<>();
data.put("d", "cd");
data.put("a", 1);
data.put("b", 0.45);
data.put("total", 333);
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> item1 = new HashMap<>();
item1.put("aa", 1);
item1.put("ee", 5);
item1.put("bb", 6);
item1.put("nn", null);
list.add(item1);
Map<String, Object> item2 = new HashMap<>();
item2.put("xxx", "000");
item2.put("q", 2);
item2.put("a", "aa");
list.add(item2);
data.put("sub", list); Map<String, Object> nData = new HashMap<>(data);
nData.put("c", null);
nData.put("abc", null); res1 = JSONObject.toJSONString(data);
res2 = JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
Assert.assertEquals("序列化结果不相等default", res1, res2); res2 = JSONObject.toJSONString(JSONObject.parseObject(
JSONObject.toJSONString(nData, SerializerFeature.SortField)),
SerializerFeature.SortField);
Assert.assertEquals("序列化结果不相等sort", res1, res2); nList = new ArrayList<>();
nList.add(item2);
nList.add(item1);
nData.put("sub", nList);
res2 = JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
Assert.assertEquals("序列化结果不相等array", res1, res2); }

  以上是fastjson库进行json序列化的处理方式,json的数据结构大部分使用可以用map进行等价,除了纯数组的结构以外。以上测试中,除了最后一个array的位置调换,导致的结果不一样之外,总体还是相等的。纠其原因,是因为原始数据结构是一致的,而fastjson从一定程度上维持了这个有序性。

  

3. fastjson维护json的有序性的实现

  很显然,让我们自行写json的工具类,还是有一定的难度的,至少要想高效完整地写json是困难的。所以,一般我们都是借助一些现有的开源类库。

  上一节中说到,fastjson维护了json一定的顺序性,但是并非完整维护了顺序性,它的顺序性要体现在,相同的数据结构序列化的json,总能得到相同的反向的相同数据结构的数据。比如,ArrayList 的顺序性被维护,map的顺序性被维护。

  但是很明显,这些顺序性是根据数据结构的特性而定的,而非所谓的字典序,那么,如果我们想维护一个保持字典序的json如何处理呢?看看下面的实现:

public class JsonObjectTest {

    @Test
public void testJsonObjectOrder() {
String res1, res2;
List<Map<String, Object>> nList;
Map<String, Object> data = new HashMap<>();
data.put("d", "cd");
data.put("a", 1);
data.put("b", 0.45);
data.put("total", 333);
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> item1 = new HashMap<>();
item1.put("aa", 1);
item1.put("ee", 5);
item1.put("bb", 6);
item1.put("nn", null);
list.add(item1);
Map<String, Object> item2 = new HashMap<>();
item2.put("xxx", "000");
item2.put("q", 2);
item2.put("a", "aa");
list.add(item2);
data.put("sub", list); Map<String, Object> nData = new HashMap<>(data);
nData.put("c", null);
nData.put("abc", null); res1 = JSONObject.toJSONString(data);
res2 = JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
Assert.assertEquals("序列化结果不相等default", res1, res2); res2 = JSONObject.toJSONString(JSONObject.parseObject(
JSONObject.toJSONString(nData, SerializerFeature.SortField)),
SerializerFeature.SortField);
Assert.assertEquals("序列化结果不相等sort", res1, res2); res2 = JSONObject.toJSONString(JSONObject.parseObject(
JSONObject.toJSONString(nData, SerializerFeature.WriteMapNullValue)),
SerializerFeature.WriteMapNullValue);
Assert.assertEquals("序列化结果不相等null", res1, res2); nList = new ArrayList<>();
nList.add(item2);
nList.add(item1);
nData.put("sub", nList);
res2 = JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
Assert.assertEquals("序列化结果不相等array", res1, res2); nList = new ArrayList<>();
nList.add(item2);
nList.add(item1);
nData.put("sub", nList);
res1 = transformDataToJSONAsOrderWay(data);
res2 = transformDataToJSONAsOrderWay(nData);
Assert.assertEquals("序列化结果不相等array-s", res1, res2); } /**
* 将原始数据转换为有序的集合
*/
private String transformDataToJSONAsOrderWay(Map<String, Object> data) {
TreeMap<String, Object> transformedData = new TreeMap<>();
for (Map.Entry<String, Object> entry : data.entrySet()) {
if(entry.getValue() == null) {
continue;
}
if(entry.getValue() instanceof List) {
TreeMap<String, Integer> tmpMap = new TreeMap<>();
List value = (List) entry.getValue();
for (int i = 0; i < (value).size(); i++) {
Object it1 = value.get(i);
// 假设只支持二维数组嵌套
tmpMap.put(transformDataToJSONAsOrderWay((Map<String, Object>) it1), i);
}
List<Object> orderedList = new ArrayList<>(tmpMap.size());
for (Integer listNo : tmpMap.values()) {
orderedList.add(value.get(listNo));
}
transformedData.put(entry.getKey(), orderedList);
continue;
}
transformedData.put(entry.getKey(), entry.getValue());
}
return JSONObject.toJSONString(transformedData);
}
}

  以上就是完整的基于fastjson实现的json字典序维持的实现了,其实就是 transformDataToJSONAsOrderWay() 方法,其原理也简单,因fastjson的有序性,依赖于输入的数据结构,那么只要维护好输入结构的字典序就好了。TreeMap 是以字典序排序key的一种数据结构,符合这需求,另外,将list这种数据结构,转化为kv这种数据结构,将整个item作为key排序后,再将其放入对应位置,从而保证了整体的顺序性。但这种list的顺序性,不一定是大家所理解的字典序,但一定可以保证得到相同的顺序。

  另外,fastjson中还考虑了对于null值的处理,比如json中有null值的数据与没有null值的数据,你说是相等呢还是不相等呢?

4. hashmap数据结构的顺序迭代原理

  map是一种kv型的数据结构存储,一般可以认为其是无序的。但我们可以额外的维护一些属性,以保证它能够以某种顺序输出数据,顺序性主要体现在进行迭代时,如使用 keyset(), values(), entrySet() 等方法。针对额外维护顺序性的数据结构而言,其迭代自然是基于其额外字段。但针对无序的hashmap这种数据结构而言,我们知道其底层数据是根据hash值乱序存储的。简单来说就是根据一个hash值,然后求余定位到一个数组下标中。即对hashmap所分配的数组对象的下标,有可能有值,有可能没有值,那么在做迭代的时候如何做呢?多次做迭代的顺序一致吗?一个最简单的思路自然是依次遍历数据的每个元素,直到数据的最大值。这样,肯定是可以保证多次遍历的顺序性的。那么,hashmap是否是这样实现的呢?

    // java.util.HashMap#forEach
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
// 1. 迭代所有数组元素
// 2. 迭代hash冲突时的链表
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
// java.util.HashMap.EntrySet#forEach
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
// keySet(), 换成了取key的处理
// java.util.HashMap.KeySet#forEach
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
// values() 换成了取value的处理
// java.util.HashMap.Values#forEach
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}

  TreeMap基于key做排序处理,最符合有序性要求,其迭代实现如下:

    // java.util.TreeMap#forEach
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
int expectedModCount = modCount;
// 从最小的key开始取,进行二叉树的中序遍历
// 因该二叉树在插入时维护了有序性,进行遍历时也就有了顺序了
for (Entry<K, V> e = getFirstEntry(); e != null; e = successor(e)) {
action.accept(e.key, e.value); if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
} /**
* Returns the successor of the specified Entry, or null if no such.
*/
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}

  LinkedHashMap是按照插入的顺序排列的一种map, 它与ArrayList这种有序性有相似性,但相对难以理解一些。因为list这种数据结构,你说先插入哪个元素,后插入哪个元素,是显而易见的。然而像map这种数据结构,你很想像它是先插入某元素,再插入另一个元素的,这是一种先入为主的概念导致的。但它并不影响我们理解map有序性的实现,LinkedHashMap的迭代实现如下:

    // java.util.LinkedHashMap#forEach
public void forEach(BiConsumer<? super K, ? super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
// 仅通过维护插入时的链表,即可实现有序迭代
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}

  ok, 到此我们分析了多个类型的map的有序性的实现。从内部解释了为什么我们使用TreeMap数据结构时,就可以使json保持字典序了。因为fastjson在写json数据时,针对map的写入,就是通过entrySet()迭代元素进行写入的了。

如何保持json序列化的顺序性?的更多相关文章

  1. Json序列化之.NET开源类库Newtonsoft.Json的研究

     一.Json简介 JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集. JSON采用完全独立于语言的文 ...

  2. 在net中json序列化与反序列化 面向对象六大原则 (第一篇) 一步一步带你了解linq to Object 10分钟浅谈泛型协变与逆变

    在net中json序列化与反序列化   准备好饮料,我们一起来玩玩JSON,什么是Json:一种数据表示形式,JSON:JavaScript Object Notation对象表示法 Json语法规则 ...

  3. C++对象的JSON序列化与反序列化探索

    一:背景 作为一名C++开发人员,我一直很期待能够像C#与JAVA那样,可以轻松的进行对象的序列化与反序列化,但到目前为止,尚未找到相对完美的解决方案. 本文旨在抛砖引玉,期待有更好的解决方案:同时向 ...

  4. C# 序列化详解,xml序列化,json序列化对比

    本文讲讲一些纯技术的东西.并且讲讲一些原理性的东西,和一般的百度的文章不一致,如果你对序列化不清楚,绝对可以很有收获. 技术支持QQ群(主要面向工业软件及HSL组件的):592132877  (组件的 ...

  5. WPF中的常用布局 栈的实现 一个关于素数的神奇性质 C# defualt关键字默认值用法 接口通俗理解 C# Json序列化和反序列化 ASP.NET CORE系列【五】webapi整理以及RESTful风格化

    WPF中的常用布局   一 写在开头1.1 写在开头微软是一家伟大的公司.评价一门技术的好坏得看具体的需求,没有哪门技术是面面俱到地好,应该抛弃对微软和微软的技术的偏见. 1.2 本文内容本文主要内容 ...

  6. 通过FeignClient接收shaded的javabean的JSON序列化

    问题说明 最近做了关于flink的需求. 现在需要通过HTTP访问FLINK的 RESTAPI, rest 接口的JSON 非常庞大而复杂. 那么怎么去完整的接收数据呢? 方法一就是手写部分需要的Ja ...

  7. Pulsar の 保证消息的顺序性、幂等性和可靠性

    原文链接:Pulsar の 保证消息的顺序性.幂等性和可靠性 一.背景 前面两篇文章,已经介绍了关于Pulsar消费者的详细使用和自研的Pulsar组件. 接下来,将简单分析如何保证消息的顺序性.幂等 ...

  8. SpringBoot整合reids之JSON序列化文件夹操作

    前言 最近在开发项目,用到了redis作为缓存,来提高系统访问速度和缓解系统压力,提高用户响应和访问速度,这里遇到几个问题做一下总结和整理 快速配置 SpringBoot整合redis有专门的场景启动 ...

  9. .Net深入实战系列—JSON序列化那点事儿

    序 当前主流的序列化JSON字符串主要有两种方式:JavaScriptSerializer及Json.net(Nuget标识:Newtonsoft.Json).JavaScriptSerializer ...

随机推荐

  1. 如何使用 K8s 两大利器"审计"和"事件"帮你摆脱运维困境?

    概述 下面几个问题,相信广大 K8s 用户在日常集群运维中都曾经遇到过: 集群中的某个应用被删除了,谁干的? Apiserver 的负载突然变高,大量访问失败,集群中到底发生了什么? 集群节点 Not ...

  2. print(end="\r") 滚动输出到屏幕

    for i in range(10000): print(i, end = "\r") print(end="\r") 滚动输出到屏幕

  3. Java基础学习之异常处理(6)

    @ 目录 1.异常是什么 1.1.Error 1.2.Exception 2.异常处理机制 2.1.捕获异常 2.2.抛出异常 3.自定义异常 3.1.自定义检查性异常 3.2.自定义运行时异常 1. ...

  4. 将一个数组转化为需要的格式,来自react官网的商品列表示例

    //原来的格式 const PRODUCTS = [ { category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Foot ...

  5. rsync 参数说明及使用参数笔记

    第1章 rsync 命令简介 rsync 是一款开源的.快速的.多功能的.可实现全量及增量的本地或远程数据镜像同步备份的优秀工具. 1.1.1 语法格式 三种模式: 1)本地模式 rsync [选项] ...

  6. J20航模遥控器开源项目系列教程(六)亚克力外壳制作

    我们的开源宗旨:自由 协调 开放 合作 共享 拥抱开源,丰富国内开源生态,开展多人运动,欢迎加入我们哈~ 和一群志同道合的人,做自己所热爱的事! 项目开源地址:https://github.com/J ...

  7. antDesign中排序sorter的坑

    antd中sorter是写在columns中的一个配置,结果为一个回调函数 如图,这是我项目中使用sorter的小例子,参数a,b分别为列表第0项数据和第1项数据.回调函数中return一个值,按照什 ...

  8. SpringBoot + SpringSecurity + Quartz + Layui实现系统权限控制和定时任务

    1. 简介   Spring Security是一个功能强大且易于扩展的安全框架,主要用于为Java程序提供用户认证(Authentication)和用户授权(Authorization)功能.    ...

  9. TCP 百万并发 数据连接测试 python+locust

    过程笔记和总结 尝试一.locust 测试百万Tcp并发 另一种方式是使用jmeter 基础环境 服务端 虚拟机:Centos7.2 jdk 1.8 客户端 虚拟机: Centos7.2 python ...

  10. Happens-Before原则到底规定了什么

    Happens-Before 规则 如何理解 Happens-Before 呢?如果望文生义(很多网文也都爱按字面意思翻译成"先行发生"),那就南辕北辙了,Happens-Befo ...