Kotlin中Json的序列化与反序列化 -- Gson、Moshi

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,那么就会收到编辑器的错误提示信息:
Snipaste_2021-05-31_14-08-02.png

那假如需要给参数传递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一筹,支持空安全以及默认值,这可以让我们的程序更加健壮。

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 32
    评论
Kotlin的数据类(data class)可以方便地进行序列化反序列化操作。可以使用Kotlin的内置库或第三方库来实现这些操作。 使用Kotlin内置库实现序列化反序列化: 1. 首先,需要在build.gradle文件添加Kotlin Serialization库的依赖: ``` implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0" ``` 2. 然后,在数据类添加注解@Serializable,以便使用Kotlin Serialization库进行序列化反序列化操作: ```kotlin @Serializable data class Person(val name: String, val age: Int) ``` 3. 对于序列化操作,可以使用toJsonString()方法将数据类转换为JSON格式的字符串: ```kotlin val person = Person("John", 30) val jsonString = Json.encodeToString(person) println(jsonString) // {"name":"John","age":30} ``` 4. 对于反序列化操作,可以使用decodeFromString()方法将JSON格式的字符串转换为数据类: ```kotlin val jsonString = """{"name":"John","age":30}""" val person = Json.decodeFromString<Person>(jsonString) println(person) // Person(name=John, age=30) ``` 使用第三方库Gson实现序列化反序列化: 1. 首先,需要在build.gradle文件添加Gson库的依赖: ``` implementation 'com.google.code.gson:gson:2.8.6' ``` 2. 然后,使用Gson对象的toJson()方法将数据类转换为JSON格式的字符串: ```kotlin val gson = Gson() val person = Person("John", 30) val jsonString = gson.toJson(person) println(jsonString) // {"name":"John","age":30} ``` 3. 对于反序列化操作,可以使用fromJson()方法将JSON格式的字符串转换为数据类: ```kotlin val jsonString = """{"name":"John","age":30}""" val person = gson.fromJson(jsonString, Person::class.java) println(person) // Person(name=John, age=30) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值