map转json(map转json不改变顺序)

map转json(map转json不改变顺序)阿里妹导读 本篇将主要介绍 json 序列化的详细流程 本文阅读的 FastJSON 源码版本为 2 0 31 一 引言 在日常开发中 我们常用 FastJSON 进行序列化和反序列化 虽然它给我们带来了便捷 但其背后的原理往往被忽视 于是一个不小心就引发了很多血案 例如 在不知其所以然的情况下 我每次使用起来也是胆战心惊的 比如抛出我经常遇到的两个问题 1 序列化操作 JSON

大家好,我是讯享网,很高兴认识大家。



图片
讯享网

阿里妹导读

本篇将主要介绍json序列化的详细流程。本文阅读的FastJSON源码版本为2.0.31。

一、引言

在日常开发中,我们常用FastJSON进行序列化和反序列化。虽然它给我们带来了便捷,但其背后的原理往往被忽视,于是一个不小心就引发了很多血案,例如:

在不知其所以然的情况下,我每次使用起来也是胆战心惊的,比如抛出我经常遇到的两个问题:

1.序列化操作:JSON.toJSONString()方法

代码中许多地方都使用了JSON.toJSONString()方法打印日志,可能会遇到转换失败的情况,比如下面一段报错:

图片

注:阿里巴巴开发规约中已经明确禁止在日志打印中直接用JSON工具将对象转换成String,所以大家还是尽量避免使用。

图片

2.反序列化操作:JSON.parseObject()方法

当一个类中嵌套了多层内部类时,JSON.parseObject() 方法是否能够准确转换?

因此,我决定遵循“深入了解才能安心使用”的原则,阅读一下 FastJSON 的源码,以便更好地理解其原理和使用时需要注意的事项。

由于内容较长,本篇将主要介绍json序列化的详细流程。本文阅读的FastJSON源码版本为2.0.31。

二、FastJSON源码阅读

1. FastJSON整体结构

FastJSON的源码结构如下,其中JSON类是源码的入口类,虽然看起来有很多方法,实则概括起来是四类方法:

图片

FastJSON中几个关键的包为:annotation包、asm包、parser包、serializer包。

2. 序列化流程

以JSON.toJSONString(),并且序列化一个JavaBean为例,整个方法序列化的时序图如下:

图片

首先,用户调用JSON.toJSONString() 方法并且传入待序列化对象,随后执行以下序列化流程:

1.创建SerializeWriter 对象:SerializeWriter对象类似于StringBuilder ,但性能上做了许多优化,用来存储序列化过程中产生的字符串。

2. 创建 JSONSerializer 对象: JSONSerializer 对象提供序列化的一个入口,持有所有具体负责对象序列化工作类的引用。然后调用具体序列化器的 write() 函数进行序列化,这也是序列化的正式开始:

3.序列化器为 null 的情况:

4.写入数据: 获取到序列化写入器后,JSONSerializer 调用该写入器的 write() 方法开始将数据写入 SerializeWriter

5.返回 JSON 字符串: 最后,SerializeWriter 将存储的字符串转换为最终的 JSON 字符串,并返回给用户。

通过这些步骤,用户最终得到了序列化后的 JSON 字符串。

下面详细介绍一下具体的代码实现流程:

1.调用JSON.toJSONString(obj)方法后,最终会走到下面的重载函数,这个函数主要干了三件事:

a.创建SerializeWriter对象 out,用于储存在序列化过程中产生的数据。

b.创建JSONSerializer 对象serializer,该对象持有所有负责具体对象序列化工作类的引用。

c. 将对象 object 解析成 string,并将结果写入到out的buffer中。
这个序列化方法实际并不是真正执行序列化操作,首先做序列化特性配置,然后追加序列化拦截器,开始执行序列化对象操作委托给了config对象查找。

2.其中serializer对象的write方法如下,这个方法主要做了两件事:

a. 通过对象的类获得对应的解析类ObjectSerializer。
b. 调用解析类的write方法序列化对象。
那么接下来就是搞懂怎么获得解析类,解析类又是怎么序列化对象的就可以了。

3.获取对象的解析类

getObjectWriter方法会内部调用重载方法,且create参数为true。

a. 首先,先调用get(clazz)方法,查看IdentityHashMap中有没有已经注册的解析类,有则直接返回。
b. 若没有,则会通过一系列判断以及create为true的条件走到createJavaBeanSerializer(clazz)方法中,创建对应的解析类并放入到IdentityHashMap中。
4.createJavaBeanSerializer(clazz)方法创建解析类。

这个方法会创建出JavaBeanSerializer对象和SerializeBeanInfo对象,SerializeBeanInfo主要是包含了java对象的字段和方法信息以及注释等,它决定了一个java对象序列化过程的输出。JavaBeanSerializer会根据SerializeBeanInfo对象中的fields字段,创建对应的成员变量的FieldSerializer。

a. 首先,通过TypeUtils获取变量 beanInfo
i. 最终会走到computeGetters方法通过getter拿到所有对象的属性信息。

ii.computeGetters方法的具体内容如下,通过判断以get开头和is开头的方法返回元素属性的集合。

b. 然后创建java类的解析器,为了提升性能,默认通过asm动态生成类来避免重复执行时的反射开销。
最终调用的还是JavaBeanSerializer的write方法写入。

5.调用解析类的write方法序列化对象

首先会根据IgnoreNonFieldGetter属性选择是否忽略没有对应字段的get方法,然后通过调用每个属性序列化器的getPropertyValueDirect方法获得对应的值。

可以看到,最终还是通过反射获取到属性的值加入到输出流中。



三、序列化注意事项

1. getter方法规范化

FastJSON序列化元素时是通过调用对象的所有以“get”或者“is”开头的方法实现的,根据上文源码中实现逻辑,应该注意以下几点:

举个🌰:类MyTest定义了一个没有对应成员变量的getNoField()方法,并且方法里有错误代码,根据源码逻辑,序列化过程中一定会调用这个方法。

测试代码输出如下:序列化失败,抛出ArithmeticException异常。
如果序列化方法加入:

SerializerFeature.IgnoreNonFieldGetter参数,则可以序列化成功。

2.  布尔类型属性命名不要用is开头

布尔类型的属性不要使用is开头,fastJons会截取is后面的名称当作属性名,造成序列化错误的情况。

举个🌰:自定义类MyTest中包含属性isActive和value,另外还写了一个没有对应成员变量的getNoField方法。

测试代码的输出结果为:属性"isActive"错误的输出了"active",还错误的输出了不存在属性noField。
注:在阿里巴巴开发手册中已经专门强调任何布尔类型的变量都不要加is前缀,容易造成解析异常。(所以说开发手册还是要多看几遍!!)

图片

3. 重复引用问题

序列化过程如果存在重复/循环引用的情况,FastJSON就会用引用标识\(ref代替,这样做的好处一是可以节省空间,二是可以避免循环引用的问题。举个🌰:</span></p><div style="line-height:1.75em;margin-bottom:24px;margin-top:24px"><span style="color:rgb(62, 62, 62);font-size:15px">这段代码的输出结果如下:可以看到重复引用的user1变成了{&quot;\)ref&quot;:&quot;$[0]&quot;}格式。

注意,这种字符串格式只有使用FastJSON才可以正确反序列化,当涉及到是有了不同的序列化框架时往往会导致失败,如果想要正常的序列化方式化,可以通过:

JSON.toJSONString(map,SerializerFeature.DisableCircularReferenceDetect)

禁止使用循环引用。

四、附录

这里给出序列化时常用到注解和SerializerFeature序列化属性。

最后,由于个人水平有限,如文章中有误解或疏漏之处,欢迎各位大佬批评指正~

1.注解

注解

含义

参数

@JSONField

该注解作用于方法上,字段上和参数上,可在进行序列化和反序列化时进行个性功能定制。

@JSonType

该注解作用于类上,对该类的字段进行序列化和反序列化时的特性功能定制。



@JSONCreator

用于标识一个类的构造函数或静态工厂方法,以便在反序列化 (将 JSON 数据转换为 Java 对象) 时使用。

@JSONPOJOBuilder

用于定义POJO的构建器类和构建方法,使得创建复杂对象变得灵活。

2.SerializerFeature枚举类

黄色标记的为常用枚举值。

         常量

                           含义

QuoteFieldNames

是否输出key值引号,默认为 true

UseSingleQuotes

输出是否使用单引号,默认为 false

WriteMapNullValue

是否输出值为 null 的字段,默认为 false

WriteEnumUsingToString

是否输出枚举值的 toString() 方法,默认为 false

是否使用 ISO8601 格式输出日期,默认为 false

WriteNullListAsEmpty

是否将 null 值的 List 输出为空数组,默认为 false

WriteNullStringAsEmpty

是否将 null 值的 String 输出为空字符串,默认为 false

WriteNullNumberAsZero

是否将 null 值的 Number 输出为 0,默认为 false

WriteNullBooleanAsFalse

是否将 null 值的 Boolean 输出为 false,默认为 false

SkipTransientField

是否跳过 transient 修饰的字段,默认为 false

SortField

是否按照字段名称排序输出,默认为 false

WriteTabAsSpecial

是否将制表符输出为 ,默认为 false

PrettyFormat

是否格式化输出,默认为 false

WriteClassName

是否输出对象的 class 名称,默认为 false

DisableCircularReferenceDetect

是否禁止循环引用检测,默认为 false

WriteSlashAsSpecial

是否将斜杠输出为 /,默认为 false

BrowserCompatible

是否输出为浏览器兼容的 JSON 格式,默认为 false

WriteDateUseDateFormat

是否按照指定的日期格式输出日期,默认为 false

DisableCheckSpecialChar

是否禁止特殊字符检测,默认为 false

NotWriteDefaultValue

是否不输出默认值字段,默认为 false

BeanToArray

是否将 JavaBean 转换为数组输出,默认为 false

WriteNonStringKeyAsString

是否将非 String 类型的 key 转换为 String 类型输出,默认为 false

NotWriteRootClassName

是否不输出根对象的 class 名称,默认为 false

DisableASM

是否禁用 ASM 库进行序列化,默认为 false

小讯
上一篇 2025-05-08 15:43
下一篇 2025-05-10 18:48

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/166766.html