说到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. Shell:子shell概念

    Blog:博客园 个人 目录 shell环境 什么是子shell 子shell的分类 shell环境 每个shell进程有一个自己的运行环境,不同的Shell进程有不同的Shell环境.Shell解析 ...

  2. protobuf 协议浅析

    目录 Protobuf 协议浅析 1. Protobuf 介绍 1.1 Protobuf 基本概念 1.2 Protobuf 的优点 1.3 Protobuf, JSON, XML 的区别 2. Pr ...

  3. 模块urllib requests json xml configparser 学习笔记

    发起http请求 获取返回值 返回值是字符串 第三方模块安装 pip install requests 返回值格式 xml  html  jaon json 功能  loads   字符串>&g ...

  4. rpl_semi_sync_master_wait_no_slave 参数研究实验

    最近在研究MySQL,刚学到半同步. 半同步的配置中,关于这两个参数: rpl_semi_sync_master_wait_no_slave rpl_semi_sync_master_wait_for ...

  5. Java及Javascript中的浮点运算

    在进行金额计算,及某些精确计算时,会出现意想不到的很多小数的情况. 对Java 采用BigDecimal,如下代码示例 package number; import java.math.BigDeci ...

  6. burp中获取token进行暴力破解

    选中线程设成1

  7. 第 13 章 StringTable详解

    目录 第 13 章 StringTable 1.String 的基本特性 1.1.String 概述 1.2.String 的基本特征 1.3.String 的底层结构 2.String 的内存分配 ...

  8. 精尽Spring MVC源码分析 - HandlerExceptionResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 【Go语言绘图】图片添加文字(二)

    这一篇将继续介绍gg库中绘制文字相关的方法,主要包括:DrawStringAnchored().DrawStringWrapped().MeasureMultilineString().WordWra ...

  10. RxJava +Retrofit 简单使用

    1.添加依赖 compile 'com.squareup.retrofit2:converter-gson:2.3.0' compile 'com.squareup.retrofit2:adapter ...