别给我讲源码,告诉我Moshi是如何实现反序列化的!

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复制代码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容