JSON is an open-standard file format or data interchange format that uses human-readable text to transmit data objects consisting of attribute-value pairs and array data types.
Java中常见的JSON解析库有Moshi
、Jackson
、Gson
等,因为Java和Kotlin互相兼容,所以这些库也可以用在Kotlin,虽然可能会有一些小坑。
我们基于Kotlin的data class
定义一个User
的Bean,age
和role
字段分被设置默认值以是之类型非空
data class User(
val name: String,
val email: String,
val age: Int = 13,
val role: Role = Role.Viewer
)
enum class Role { Viewer, Editor, Owner }
接下来,我们将下列JSON对象解析成User
{
"name" : "John Doe",
"email" : "[email protected]"
}
先使用Gson写一个TestCase:
class JsonUnitTest {
private val jsonString = """
{
"name" : "John Doe",
"email" : "[email protected]"
}
""".trimIndent()
@Test
fun gsonTest() {
val user = Gson().fromJson(jsonString, User::class.java)
assertEquals("John Doe",user.name)
assertEquals(null, user.role)
assertEquals(0, user.age)
// User(name=John Doe, [email protected], age=0, role=null)
}
}
上面的Test运行成功了。Gson在反序列化过程中,将基本型age
设置了默认值0,将引用型role
设置了null,这破坏了kotlin的非空类型编译期检查的限制,我们在调用user.role
时可能出现crash。所以在Kotlin中进行反序列化时,我们希望能够遵循Kotlin的规范,识别这种非空类型。
Kotlinx.serialization
Kotlinx.serialization是Jetbrains针对Kotlin的推出序列化库。通过为KClass添加@Serializable
注解,配合其配套的Kotlin Compiler Plugin,可以在编译期为在伴生对象中生成serializer()
方法,serializer()
方法返回一个KSerializer类型的序列化器。
KSerializer可以进行多种类型数据的序列化反序列化,例如 JSON、CBOR
以及Protobuf
Kotlinx.serialization
还支持Kotlin MPP的跨平台使用,可以在JVM之外工作,例如用在Kotlin/Native或JavaScript中。
Kotlinx.serialization
需要 Kotlin 1.3.30
以上才可使用,如上所述,除了依赖runtime库以外, 还需要以来给一个Compiler Plugin用来进行字节码插桩。字节码插桩的一个直接好处就是可以实现零反射,性能更好。
我们基于Kotlinx.serialization
测试一下上面的TestCase:
@Serializable
data class User(
val name: String,
val email: String,
val age: Int = 13,
val role: Role = Role.Viewer
)
enum class Role { Viewer, Editor, Owner }
class JsonUnitTest {
private val jsonString = """
{
"name" : "John Doe",
"email" : "[email protected]"
}
""".trimIndent()
@Test
fun gsonTest() {
val user = Gson().fromJson(jsonString, User::class.java)
assertEquals("John Doe", user.name)
assertEquals(null, user.role)
assertEquals(0, user.age)
// User(name=John Doe, [email protected], age=0, role=null)
}
@Test
fun jsonTest() {
val user = Json.parse(User.serializer(), jsonString)
assertEquals("John Doe", user.name)
assertEquals(Role.Viewer, user.role)
assertEquals(13, user.age)
// User(name=John Doe, [email protected], age=13, role=Viewer)
}
}
测试通过可以看见Kotlinx.serialization
反序列化结果中的缺省值是data class
中定义的值,更符合预期。
Kotlinx.serialization + Retrofit
我们都经常使用Retrofit+Gson的组合进行HTTP请求以及对请求结果进行反序列化。 如今我们也可以为Retrofit 添加 新的ConverterFactory
,以适配Kotlinx.serialization
的使用。
我肯可以定义自己的ConverterFactory
,也可以使用第三方现成的,例如J神已将帮我们写好的一个https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter
val contentType = "application/json".toMediaType()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.example.com")
.addConverterFactory(
Json(JsonConfiguration(strictMode = false))
.asConverterFactory(contentType))
.build()
可以通过JsonConfiguation
进行配置,例如打开/关闭编码默认值,启用/禁用strictMode
等。默认情况下strictMode处于启用状态,此时会检查JSON中是否存在unknown的key,并且不运行String像num类型的转换等。一个推荐做法是在debug模式下开启严格模式,在release包中关闭,可以提高线上的容错性。
最后
本文只是介绍了Kotlinx.serialization
的基本用法,想了解更多内容可以参考官网以及 KotlinConf 2019: Design of Kotlin Serialization by Leonid Startsev