Kotlin中Json的序列化与反序列化 -- GsonMoshi
Posted 乐翁龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin中Json的序列化与反序列化 -- GsonMoshi相关的知识,希望对你有一定的参考价值。
文章目录
Kotlin中Json的序列化与反序列化 – Gson、Moshi
在App的开发中避免不了需要和Json格式的数据打交道,这节我们来看下Json相关的序列化和反序列化的内容。同时注意我们使用Kotlin来进行示例,来进一步理解下Kotlin的空安全设计。
实体类
这里我们准备两个实体类,Car和Driver。购买汽车会随机赠送一个驾驶员:
data class Car(
val brand: String, //汽车品牌
val driver: Driver, //随车赠送驾驶员
)
data class Driver(
val name: String,
val age: Int,
)
注意:
1、kotlin的数据类;
2、参数为非空类型;
以上Car对象传参的时候brand和driver参数都不可为null。如果要强行给brand或者driver参数赋值为null,那么就会收到编辑器的错误提示信息:
那假如需要给参数传递null值的情况下该怎么处理呢?给参数类型后添加 ? 即可,如下,那么传参的时候都可以传递null进去了:
data class Car(
val brand: String?, //汽车品牌
val driver: Driver?, //随车赠送驾驶员
)
那么使用的时候我们可以使用if-else判空来处理,或者使用 ?. 或者 ?.let 操作符来调用,好了,这就是kotlin表面上的空安全设计了。
集成方式
Gson
Gson的Github地址为:https://github.com/google/gson
implementation("com.google.code.gson:gson:2.8.7")
Moshi
Moshi的Github地址为:https://github.com/square/moshi
implementation("com.squareup.moshi:moshi:1.12.0")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.12.0")
Moshi的集成多了kapt的codegen组件,该组件可以通过注解来生成相关解析的代码,同时性能也有提升,建议使用该方式。
非空类型
注意:这里使用Car的非空数据实体。
序列化
在非空类型下,Gson和Moshi的序列化使用方式上都很简单,我们使用如下Car实例,分别使用Gson和Moshi来进行序列化:
val carBean = Car(
brand = "Benz",
driver = Driver(
name = "XiaoMing",
age = 20
)
)
//Gson序列化
val gson = Gson()
val toJson = gson.toJson(carBean)
Log.e("Gson", "toJson ==> $toJson")
//Moshi序列化
val moshi = Moshi.Builder().build()
val toJson = moshi.adapter(Car::class.java).toJson(carBean)
Log.e("Moshi", "toJson ==> $toJson")
日志打印分别如下:
Gson: toJson ==> "brand":"Benz","driver":"age":20,"name":"XiaoMing"
Moshi: toJson ==> "brand":"Benz","driver":"name":"XiaoMing","age":20
反序列化
首先,Koltin使用Moshi进行反序列化我们需要给相应的实体类添加注解,也就是给Car和Driver数据类添加如下代码:
@JsonClass(generateAdapter = true)
我们将json数据放到asset文件夹下,然后读取出来并使用Gson、Moshi来分别进行反序列化,asset文件夹下的data.json文件如下:
"brand": "Benz"
这里我们没有返回driver的相关数据,那么在Kotlin设置了非空参数类型的情况下,解析会出什么问题呢,请带着该疑问继续阅读下文?
读取该文件并转换为字符串的代码如下:
fun getJsonStr(context: Context): String
val inputStream = context.assets.open("data.json")
val inputStreamReader = InputStreamReader(inputStream)
val bufferedReader = BufferedReader(inputStreamReader)
val stringBuilder = StringBuilder()
var line: String
while (true)
line = bufferedReader.readLine() ?: break
stringBuilder.append(line)
bufferedReader.close()
return stringBuilder.toString()
我们分别使用Gson和Moshi来进行解析:
//获取到Json字符串
val jsonStr = getJsonStr(this)
//Gson解析
val gson = Gson()
val fromJson = gson.fromJson(jsonStr, Car::class.java)
Log.e("Gson", "fromJson ==> $fromJson")
Log.e("Gson", "fromJson.Driver.name ==> $fromJson.driver.name")
//Moshi解析
val moshi = Moshi.Builder().build()
val fromJson = moshi.adapter(Car::class.java).fromJson(jsonStr)
Log.e("Moshi", "fromJson ==> $fromJson")
Log.e("Moshi", "fromJson.Driver.name ==> $fromJson?.driver?.name")
在使用Gson解析的情况下,输出信息的时候出现了崩溃,打印日志如下:
Gson: fromJson ==> Car(brand=Benz, driver=null)
androidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String xxx.Driver.getName()' on a null object reference
首先是第一行解析后的Car对象的打印日志,Car的数据类型中我们定义driver参数是不能为null的,结果经过gson解析后却被赋值为null了,好像绕过了Kotlin的空安全机制。然后就下下一步解析获取driver.name的时候不出意外的出现了NullPointerException。
再看Moshi的解析情况,在打印Driver.name的时候,所有的参数都需要我们判空,但是解析还是出现了崩溃,打印日志如下:
com.squareup.moshi.JsonDataException: Required value 'driver' missing at $
由于Car的driver参数不可为空,所以直接在解析阶段就出现了JsonDataException。
但是如果我们给driver设置默认值后,会有什么情况呢?
@JsonClass(generateAdapter = true)
data class Car(
val brand: String, //汽车品牌
val driver: Driver = Driver(
name = "Default",
age = 18
), //随车赠送驾驶员
)
还是使用上文不包含driver的json数据,最后成功解析出包含了默认数据的实例:
Moshi: fromJson ==> Car(brand=Benz, driver=Driver(name=Default, age=18))
Moshi: fromJson.Driver.name ==> Default
结论
在非空数据类型下,如果参数没有设置默认值,那么由于Json数据的不规范:
- Gson会解析出可能包含null数据的对象,从而绕过了Kotlin的空安全机制,导致调用null对象的时候未判空而崩溃。
- Moshi则直接在解析的时候就报错崩溃了。
而在【非空参数】设置了【默认值】的情况下,Moshi会成功解析,并使用默认的参数值。Gson还是会解析出null对象。
可空类型
注意:这里我们使用driver参数可为空的Car实体。
序列化
我们使用可空的driver参数来进行示例,如下:
val carBean = Car(
brand = "Benz",
driver = null,
)
//Gson序列化
val gson = Gson()
val toJson = gson.toJson(carBean)
Log.e("Gson", "toJson ==> $toJson")
//Moshi序列化
val moshi = Moshi.Builder().build()
val toJson = moshi.adapter(Car::class.java).toJson(carBean)
Log.e("Moshi", "toJson ==> $toJson")
日志打印分别如下:
Gson: toJson ==> "brand":"Benz"
Moshi: toJson ==> "brand":"Benz"
反序列化
还是使用上文的json字符串,我们解析试下:
//获取到Json字符串
val jsonStr = getJsonStr(this)
//Gson解析
val gson = Gson()
val fromJson = gson.fromJson(jsonStr, Car::class.java)
Log.e("Gson", "fromJson ==> $fromJson")
Log.e("Gson", "fromJson.Driver.name ==> $fromJson.driver?.name")//在调用name的时候,drivier需要使用?.的操作符
//Moshi解析
val moshi = Moshi.Builder().build()
val fromJson = moshi.adapter(Car::class.java).fromJson(jsonStr)
Log.e("Moshi", "fromJson ==> $fromJson")
Log.e("Moshi", "fromJson.Driver.name ==> $fromJson?.driver?.name")
这时候由于driver参数可为空,所以在调用driver对象的时候,就必须要求使用 ?. 的方式了。
日志打印分别如下:
Gson: fromJson ==> Car(brand=Benz, driver=null)
Gson: fromJson.Driver.name ==> null
Moshi: fromJson ==> Car(brand=Benz, driver=null)
Moshi: fromJson.Driver.name ==> null
但是如果driver参数允许为空,同时我们又设置了默认值的情况下会有什么结果呢?
@JsonClass(generateAdapter = true)
data class Car(
val brand: String, //汽车品牌
val driver: Driver? = Driver(//和之前相比,现在是可空的参数
name = "Default",
age = 18
), //随车赠送驾驶员
)
日志打印分别如下:
Gson: fromJson ==> Car(brand=Benz, driver=null)
Gson: fromJson.Driver.name ==> null
Moshi: fromJson ==> Car(brand=Benz, driver=Driver(name=Default, age=18))
Moshi: fromJson.Driver.name ==> Default
结论
如果相关参数允许为空,那么也就完全用到了Kotlin的空安全机制。
如果相关参数设置了默认值,Moshi会解析为默认值,Gson还是会解析为null。
总结
从基础的集成及使用上来说,两者都很方便。
但是从Kotlin的角度上来说的话,Moshi还是略胜Gson一筹,支持空安全以及默认值,这可以让我们的程序更加健壮。
以上是关于Kotlin中Json的序列化与反序列化 -- GsonMoshi的主要内容,如果未能解决你的问题,请参考以下文章