Moshi是什么
一句话描述
Moshi就是一个实现了对Json序列化和反序列化的开源库
大名鼎鼎的Gson大家肯定都知道,Moshi本质上就是干了和他一样的事
( 本文是建立在对Moshi使用有一些简单了解的基础上,如果还没有了解过Moshi,建议看下这篇文章新一代Json解析库Moshi使用及原理解析)
为什么使用Moshi
一句话描述
Gson是一个针对Java的Json序列化和反序列化库
Moshi是一个针对Kotlin的Json序列化和反序列化库
在各种更高级语言涌现的今天,特别是kotlin的诞生,虽然本质上还是java那一套,但各种方便易用的api和语法糖,让人大呼爽歪歪。而老牌的Gson在处理Kotlin序列化问题上显得有些力不从心,主要有两个问题:
(1)空安全问题
Kotlin对变量分为可空类型和不可空类型,而Java中基本上所有变量都是可空的(除了@NonNullable标签)。如果Json中一个变量值是null,但Kotlin声明这个变量是非空,Gson仍然会把这个变量赋值为null
(2)默认参数失效
默认参数是Kotlin的一个新语法。是在定义一个data类时,直接在构造函数中赋给参数默认值。例如:
class People(val name:String, val age:Int = 18)复制代码
当Json是:
{ "name" : "哈哈哈"}复制代码
根据Kotlin的语法,期望得到的对象是
People(name="哈哈哈",age = 18)复制代码
但实际上Gson会解析成
People(name="哈哈哈",age = 0)复制代码
这样就造成默认参数age = 18的丢失
为了让Json解析更加符合kotlin的语法规范,于是选择使用Moshi
Moshi是如何实现反序列化的
这时候好奇的哥哥可能就要问了
为什么Moshi的解析更符合Kotlin的语法规范呢? 你说符合就符合啊?
别急别急,想弄清这个问题,需要了解一下Moshi是如何实现反序列化的
我们先看一下Moshi反序列化的简单使用:
// 需要反序列化的jsonval json = "{ "name": "张三", "age": 18, "email": "helloword@163.com"}"// Model类class People(val name:String,val age:Int ){val email : String = ""}// 反序列化fun parseModel(json: String): People? { val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() val adapter = moshi.adapter(People::class.java) val people = adapter.fromJson(json) return people}复制代码
( Model类里藏了一个坑,后面揭晓 ^ ^ )
没有用过Moshi的哥哥可能很反感这种行为,“来不来就给我整几行代码?我是看了代码就能记住的人吗?!”
别急别急,我这就一点一点给您掰开了揉碎了说,您就舒舒服服躺好嘞~
这里陌生的东西主要有两个。KotlinJsonAdapterFactory和adapter。
①KotlinJsonAdapterFactory 是生成JsonAdapter的工厂
② val adapter = moshi.adapter(People::class.java)实际上就是传了个class type给KotlinJsonAdapterFactory,然后工厂创建出adapter
③ adapter调用自己的fromJson和toJson方法完成序列化和反序列化过程。
把上面的代码翻译成流程图就是:
Moshi反序列化过程就是这么简单!
校验对象
了解了Moshi的反序列化流程,我们来回答一下上面哥哥的问题
为什么说用Moshi更符合kotlin语法规范呢?
我们先回到Moshi使用的那个例子,这个反序列化的结果是什么呢?
是People(name="张三", age = 18) email = "helloworld@163.com"
还是People(name="张三", age = 18) email = ""
我们从Kotlin语法分析,email是val不可变的。序列化实际上就是创建一个对象然后根据json进行赋值,那么Moshi会对不可变的email进行赋值吗
答案是不会。
胆大心细脸皮厚的哥哥可能已经看到上面流程图中KotlinJsonAdapterFactory在生成JsonAdapter过程中,有一个「检验对象是否合法」的过程,我偷偷贴这个过程中的一行源码:
// 位于:KotlinJsonAdapterFactory#create() if (property !is KMutableProperty1 && parameter == null) continue复制代码
哎哎哎,我不是标题党,这不是源码,是英语英语,不信我给你解释解释
propery-- 表示数据类中所有的成员属性。用上面的例子来说就是val name:String,val age:Int和var email : String = ""
KMutableProperty1-- 是Kotlin中的类型对象。表示的是var这个可变类型
parameter-- 表示构造函数中的参数。用上面的例子就是val name:String,val age:Int(这里联系上下文知道parameter == null是用来判断property是不是构造函数中声明的成员属性)
continue-- 跳过。不对这个属性反序列化了
所以用伟大中华人民共和国56个民族之一的汉族语言来说就是:
如果一个成员属性,他是不可变的(也就是val)并且不是构造函数中声明的,就跳过它
看到了吧,KotlinAdapterJsonFactory实际上就是区别于Gson的关键之一,它对类对象做了一些校验,使Moshi更加符合Kotlin语法
真正实现
KotlinJsonAdapter是Moshi的另一个核心,是Model的序列化和反序列化的真正实现
adapter的核心是binding
这里我们可以类比一下Model。一个Model对应一个adapter,Model中的成员属性就是一个个Binding
Binding负责基本数据类型的解析。IntBinding里面有个IntAdapter,负责解析json中的int类型。StringBinding里有个StringAdapter,负责解析json中的string类型。
总的来说,Binding就是负责拿到json中字段的类型和值,然后自己保存起来
至于json中字段的类型和值是如何被识别的嘛,Moshi基本上就是抄Gson的JsonReader和JsonWriter
KotlinJsonAdapter通过反射生成一个类对象,这些binding把成员属性的值一个个赋值到属性上,最终生产出一个model对象。
等等等等,通过反射生成对象?咋这么抽象呢?
很简单啦。其实就是调用Android的Constructor类中newInstance(*args)方法
只要有类对象,生成实例对象就是一句话的事
People::class.java.constructors[1].newInstance("张三", 20)// constructors[0]是参数最多的构造函数// 第三个参数表示第几个构造参数属性用默认参数的值// 所以结果是:People("张三", 18)People::class.java.constructors[0].newInstance("张三", 20, 2, null)复制代码
到目前为止,我们基本上顺了一遍Moshi反序列化的过程。尽量不贴大段代码,是不希望大家在好不容易啃完一遍源码之后,过几个星期就感觉似是而非,似懂非懂
所以我希望能给大家一个简单的体系, 有一些不得不用源码的地方也是尽量给出通俗的解释。
代码只是细节的实现,是知识体系上的枝叶。如果大家想再深入了解某些细节,可以阅读以下方法
// factory生成adapterKotlinJsonAdapterFactory#create()// adapter序列化和反序列化:KotlinJsonAdapter#fromJsonKotlinJsonAdapter#toJson// 反射生成对象KCallableImpl#callBy#callDefaultMethodCallerImpl>Constructor#callConstructor#newInstance复制代码