上一篇我们对比介绍了 Gson 和 Kotlinx.serialization,很多小伙伴在后台留言说,moshi 呢?
Moshi 怎么解决 Kotlin 数据类的问题?
首先必须说的是,Moshi 这个框架也算是 Jake 大神的良心之作了,无论从功能上,还是从使用的角度,这个框架值得推荐。
我们上一篇文章提到 Gson 不认识 Kotlin,所以对 Kotlin 的数据类几乎没有支持,这包括构造器的默认值、初始化逻辑的调用等等,而 Moshi 则类似于 Kotlinx.serialization,为我们解决了这一问题。
其实如果我们不看 Moshi 和 KS 的实现,我们单纯猜测他们要如何解决这一难题的话,无非就是使用 Kotlin 反射或使用注解处理器等方法来获取到 Kotlin 类的主构造器,以及它的参数类型和参数名(注意Kotlin反射是可以获取到参数名的哦~当然如果你用了混淆,那么这里会有问题),所以对于下面的情况:
代码语言:javascript复制data class Data(val id: Int, val name: String, val age: Int)
即便我们的 Json 中 K-V 的顺序是乱序的:
代码语言:javascript复制{"name": "bennyhuo", "id": 1000, "age": 20}
使用 Kotlin 反射,一样可以正确的将 Json 的数据结构与 Data
的主构造器的参数一一正确对应。使用注解处理器那就更不用说了。Moshi 的解决方法就是这样,它为我们提供了两种选择,你可以选择使用 Kotlin 反射,那样的话你需要忍受 Kotlin 反射 2.5M 的 jar 包以及相对较慢的运行时开销;你也可以选择注解处理器的方式,
@JsonClass(generateAdapter = true)
data class Data(val id: Int, val name: String, val age: Int)
Moshi 可以为前面的 Data
生成一个 Adapter
:
class DataJsonAdapter(moshi: Moshi) : JsonAdapter<Data>() {
private val options: JsonReader.Options = JsonReader.Options.of("id", "name", "age")
...
override fun fromJson(reader: JsonReader): Data {
var id: Int? = null
var name: String? = null
var age: Int? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.selectName(options)) {
0 -> id = intAdapter.fromJson(reader) ?: throw JsonDataException("Non-null value 'id' was null at ${reader.path}")
1 -> name = stringAdapter.fromJson(reader) ?: throw JsonDataException("Non-null value 'name' was null at ${reader.path}")
2 -> age = intAdapter.fromJson(reader) ?: throw JsonDataException("Non-null value 'age' was null at ${reader.path}")
-1 -> {
// Unknown name, skip it.
reader.skipName()
reader.skipValue()
}
}
}
reader.endObject()
var result = Data(
id = id ?: throw JsonDataException("Required property 'id' missing at ${reader.path}"),
name = name ?: throw JsonDataException("Required property 'name' missing at ${reader.path}"),
age = age ?: throw JsonDataException("Required property 'age' missing at ${reader.path}"))
return result
}
override fun toJson(writer: JsonWriter, value: Data?) {
...
}
}
大家可以自己试一试,考虑篇幅我只保留了 fromJson
的实现,大家可以参考。
Kotlin.serialization 怎么解决 Kotlin 数据类的问题?
那么同样的问题我们再问一问 KS。KS 的思路实际上与 Moshi 的注解处理器类似,只不过它因为更靠近 Kotlin 官方,是嫡系,因此它可以把一些工作放到编译器里面做。
代码语言:javascript复制@Serializable
data class Data(val id: Int, val name: String, val age: Int)
同样用 Data
这个类为例,我们按照 KS 的要求配置好之后,编译,我们可以在 Data
的字节码当中找到一些额外的东西:
public static final class $serializer implements KSerializer {
public static final Data.$serializer INSTANCE;
private static final KSerialClassDesc $$serialDesc;
public Data update(@NotNull KInput input, @NotNull Data old) { ... }
public Object update(KInput var1, Object var2) { ... }
public KSerialClassDesc getSerialClassDesc() { ... }
public void save(@NotNull KOutput output, @NotNull Data obj) { ... }
public Data load(@NotNull KInput input) { ... }
...
}
$serializer
就是 KS 为 Data
生成的默认的序列化类,这样的做法其实与注解处理器有异曲同工之妙,只不过直接生成字节码的方式可以修改原有的类,因此作为 Data
的内部类, $serializer
可以访问 Data
的私有成员(如果有的话)。
Moshi 和 Kotlin.serialization 的对比
这二者从能力上,对 Kotlin 的支持其实差异不大,下面我简单它们适合的场景。
- KS 的优势是支持 Kotlin 的 Multiplatform,对于需要多平台移植的 Kotlin 代码,使用 KS 显然更合适。
- Moshi 的优势是兼容 Java ,毕竟 Kotlin 的代码 90% 仍然跑在 Jvm 甚至 Android 上,所以如果你的 Kotlin 代码与 Java 代码混合运行在 Jvm 上面,那么考虑使用 Moshi。
对啦,我的 Kotlin 新课 “基于 GitHub App 业务深度讲解 Kotlin1.2高级特性与框架设计” 上线之后,大家普遍反映有难度,有深度,如果哪位朋友想要吊打 Kotlin,不妨来看看哦!
https://coding.imooc.com/class/232.html