高效的 Json 解析框架 kotlinx.serialization

这篇具有很好参考价值的文章主要介绍了高效的 Json 解析框架 kotlinx.serialization。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、引出问题

你是否有在使用 Gson 序列化对象时,见到如下异常:

Abstract classes can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type.

什么时候会出现如此异常。下面举个栗子:

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

sealed class Gender
object Male: Gender()
object Female: Gender()

data class Student(
    val id: Int,
    val name: String,
    val gender: Gender
)

fun main() {
    val list1 = listOf(
        Student(1001, "Jimy", Male),
        Student(1002, "Lucy", Female),
        Student(1003, "HanMeimei", Female),
        Student(1004, "LiLei", Male)
    )
    println("list1: $list1")
    val jsonString = Gson().toJson(list1)
    println("jsonString: $jsonString")
    try {
        val typeToken = object : TypeToken<List<Student>>() {}.type
        val list2: List<Student> = Gson().fromJson(jsonString, typeToken)
        println("list2: $list2")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

上面的代码,执行结果如下:

list1: [Student(id=1001, name=Jimy, gender=serialize.gson.Male@79fc0f2f), Student(id=1002, name=Lucy, gender=serialize.gson.Female@50040f0c), Student(id=1003, name=HanMeimei, gender=serialize.gson.Female@50040f0c), Student(id=1004, name=LiLei, gender=serialize.gson.Male@79fc0f2f)]
jsonString: [{"id":1001,"name":"Jimy","gender":{}},{"id":1002,"name":"Lucy","gender":{}},{"id":1003,"name":"HanMeimei","gender":{}},{"id":1004,"name":"LiLei","gender":{}}]
catch: Abstract classes can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type. Class name: serialize.gson.Gender

从这个输出结果,我们可以看到两个问题:

  1. list1 经过序列化,得到的 jsonString 中, gender 属性是空。
  2. jsonString 反序列化过程中发生了异常。

二、解决问题

异常信息已经指明了问题的解决方案

Abstract classes can't be instantiated! Register an InstanceCreator or a TypeAdapter for this type.

抽象类无法实例化!为此类型注册 InstanceCreator 或 TypeAdapter。

其实也很好理解。 sealed classabstract classinterface 都是抽象的,不能直接被实例化。对于抽象类的子类或者接口的实现类,应该明确制定序列化和反序列化的规则。由于我们没有注册 TypeAdapter, 默认的 TypeAdapter ,将 Gender 属性序列化为了空对象。在进行反序列化时,空对象不知道应该如何反序列化,所以抛出了如下的异常。

解决办法之一,在序列化和反序列化时,需要使用 Gson 的 registerTypeAdapterregisterTypeHierarchyAdapter 方法来处理密封类的子类。

首先为抽象类/接口创建一个 TypeAdapter

class GenderTypeAdapter: TypeAdapter<Gender>() {
    override fun write(out: JsonWriter?, value: Gender?) {
        out?.value(value?.javaClass?.name)
    }

    override fun read(`in`: JsonReader?): Gender {
        return when(val className = `in`?.nextString()) {
            Male::class.java.name -> Male
            Female::class.java.name -> Female
            else -> throw IllegalArgumentException("Unknown class name: $className")
        }
    }
}

然后为 Gson 对象注册该 typeAdapter

fun main() {
    val list1 = listOf(
        Student(1001, "Jimy", Male),
        Student(1002, "Lucy", Female),
        Student(1003, "HanMeimei", Female),
        Student(1004, "LiLei", Male)
    )
    println("list1: $list1")

    // I'm here
    val jsonString = GsonBuilder().registerTypeAdapter(Gender::class.java, GenderTypeAdapter()).create().toJson(list1)
    
    println("jsonString: $jsonString")
    try {
        val typeToken = object : TypeToken<List<Student>>() {}.type

        // I'm here
        val list2: List<Student> = GsonBuilder().registerTypeAdapter(Gender::class.java, GenderTypeAdapter()).create().fromJson(jsonString, typeToken)
        
        println("list2: $list2")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

此时执行结果如下:

list1: [Student(id=1001, name=Jimy, gender=serialize.gson.Male@79fc0f2f), Student(id=1002, name=Lucy, gender=serialize.gson.Female@50040f0c), Student(id=1003, name=HanMeimei, gender=serialize.gson.Female@50040f0c), Student(id=1004, name=LiLei, gender=serialize.gson.Male@79fc0f2f)]
jsonString: [{"id":1001,"name":"Jimy","gender":"serialize.gson.Male"},{"id":1002,"name":"Lucy","gender":"serialize.gson.Female"},{"id":1003,"name":"HanMeimei","gender":"serialize.gson.Female"},{"id":1004,"name":"LiLei","gender":"serialize.gson.Male"}]
list2: [Student(id=1001, name=Jimy, gender=serialize.gson.Male@79fc0f2f), Student(id=1002, name=Lucy, gender=serialize.gson.Female@50040f0c), Student(id=1003, name=HanMeimei, gender=serialize.gson.Female@50040f0c), Student(id=1004, name=LiLei, gender=serialize.gson.Male@79fc0f2f)]

Ok, 没有问题。
那... registerTypeAdapterregisterTypeHierarchyAdapter 两个方法有什么区别呢?

它们的主要区别在于注册对象的范围不同。

  • registerTypeAdapter 用于为特定的 Java 对象或类型注册自定义的序列化和反序列化逻辑。使用 TypeAdapter,可以在 Gson 序列化或反序列化特定对象或类型时,对其进行自定义处理。TypeAdapter 只会被应用于所注册的对象或类型。

  • registerTypeHierarchyAdapter 方法则是用于为特定类及其子类注册自定义的序列化和反序列化逻辑。使用 registerTypeHierarchyAdapter 方法,可以为一个类及其子类注册自定义的序列化和反序列化逻辑,这个逻辑将被应用于该类及其所有子类。这在处理一组类继承结构时非常有用。

  • 在使用 registerTypeHierarchyAdapter 方法时,需要注意一点,即 Gson 会遍历所有的子类来找到最合适的 TypeAdapter,因此要确保该 TypeAdapter 能够正确处理所有的子类。如果某个子类没有对应的处理逻辑,或者处理逻辑有误,就可能导致序列化或反序列化失败。

因此,如果要为一组类继承结构注册自定义的序列化和反序列化逻辑,可以使用 registerTypeHierarchyAdapter 方法;如果只需要为某个具体的 Java 对象或类型注册自定义的序列化和反序列化逻辑,则可以使用 TypeAdapter。

三、用 kotlinx.serialization 进行Kotlin JSON序列化

Gson 是针对 java 对象的序列化框架。基于 Kotlin 对象使用 Gson 框架,会失去 Kotlin 的一些重要特性,比如:

  • 非空类型安全。比如 Kotlin 类的属性定义为非空类型时,仍然可以将一个 null 赋值给它创建一个对象。
  • 参数默认值没有效果。Kotlin 属性可以赋予默认值。但是当使用 Gson 时,将会失去效果。

修改之前的例子:

sealed class Gender
object Male: Gender()
object Female: Gender()

data class Student(
    val id: Int,
    val name: String = "unknown",
    val gender: Gender
)

fun main() {
    val json = """ 
       {
           "id": 1005
       }
    """.trimIndent()
    try {
        val stu = Gson().fromJson(json, Student::class.java)
        println("stu: $stu")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

这里我们在定义 Student 类是,给 name 属性指定了一个默认值 unknown, 在进行反序列化时,没有指定 name 和 gender, 看看执行结果:

stu: Student(id=1005, name=null, gender=null)

结果也表明,name 的默认值没有成功,并且 name 和 gender 都赋值为 null 了。

针对上述问题有很多解决办法。但是这里,我要介绍一个新的 Json 框架,Kotlin 团队开发的一个 native 支持的库 kotlinx.serialization, 这个库支持JVM,JavaScript,Native所有平台,同时也支持多种格式的序列化——JSON,CBOR,protocol buffers等等。

3.1 kotlinx.serialization 的使用

  1. plugins 引入:
plugins {
    id("org.jetbrains.kotlin.plugin.serialization") version("1.4.30")
}
  1. dependencies 引入:
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
  1. 通过添加 @Serializable 注解,给类进行序列化
package serialize.ktxSerialization

import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
sealed class Gender

@Serializable
object Male: Gender()

@Serializable
object Female: Gender()

@Serializable
data class Student(
    val id: Int,
    val name: String = "unknown",
    val gender: Gender
)

注意:所涉及到的抽象类极其子类都需要加上该注解。

测试代码:

fun main() {
    val json = """
       {
         "id": 1005
       }
    """.trimIndent()

    try {
        val stu = Json.decodeFromString<Student>(json)
        println("stu: $stu")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

反序列化的关键方法:

Json.decodeFromString()

执行报错了:

catch: Field 'gender' is required for type with serial name 'serialize.ktxSerialization.Student', but it was missing at path: $

错误信息指出: gender 属性是必须的。那我们应该如何该如何添加 gender 属性呢?
不急,我们先序列化看看生成的是什么。

fun main() {
    val student = Student(1006, "James", Male)
    val jsonString = Json.encodeToString(student)
    println("jsonString: $jsonString")
}

执行结果如下:

jsonString: {"id":1006,"name":"James","gender":{"type":"serialize.ktxSerialization.Male"}}

我们看到,Student 对象序列化之后, gender 对应的 value 是
{"type":"serialize.ktxSerialization.Male"}
这里是完整的包名类名。

到这里,我们再手动构造验证一下:

fun main() {
    val json = """
       {
         "id": 1005,
         "gender": {"type": "serialize.ktxSerialization.Female"}
       }
    """.trimIndent()
    try {
        val stu = Json.decodeFromString<Student>(json)
        println("stu: $stu")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

执行结果:

stu: Student(id=1005, name=unknown, gender=serialize.ktxSerialization.Female@36d64342)

可以看到,反序列化成功,生成的对象,name 属性赋了默认值。

另外需要注意的是:如果在定义 Kotlin 的类中某个属性,没有指定默认值,即便该属性是可空类型,反序列化时也一定要赋值才能执行成功。

修改下例子:

package serialize.ktxSerialization

import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
sealed class Gender

@Serializable
object Male: Gender()

@Serializable
object Female: Gender()

@Serializable
data class Student(
    val id: Int,
    val name: String?,  // 注意这里
    val gender: Gender
)

fun main() {
    val json = """
       {
         "id": 1005,
         "gender": {"type": "serialize.ktxSerialization.Female"}
       }
    """.trimIndent()
    try {
        val stu = Json.decodeFromString<Student>(json)
        println("stu: $stu")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

我把 name 设置为可空类型,但是没有默认值。这时反序列化是会失败的:

catch: Field 'name' is required for type with serial name 'serialize.ktxSerialization.Student', but it was missing at path: $

给 name 属性赋值为 null, 则执行成功

fun main() {
    val json = """
       {
         "id": 1005,
         "name", null,
         "gender": {"type": "serialize.ktxSerialization.Female"}
       }
    """.trimIndent()
    try {
        val stu = Json.decodeFromString<Student>(json)
        println("stu: $stu")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

结果:

stu: Student(id=1005, name=null, gender=serialize.ktxSerialization.Female@340f438e)

3.2 用 kotlinx.serialization 解决本文开头的问题

对于本文开头引出的问题,如果使用 kotlinx.serialization,则该问题即可轻松解决。
直接上代码:

fun main() {
    val list1 = listOf(
        Student(1001, "Jimy", Male),
        Student(1002, "Lucy", Female),
        Student(1003, "HanMeimei", Female),
        Student(1004, "LiLei", Male)
    )
    println("list1: $list1")
    val jsonString = Json.encodeToString(list1)
    println("jsonString: $jsonString")
    try {
        val list2 = Json.decodeFromString<List<Student>>(jsonString)
        println("list2: $list2")
    } catch (ex: Exception) {
        println("catch: ${ex.message}")
    }
}

执行结果:

list1: [Student(id=1001, name=Jimy, gender=serialize.ktxSerialization.Male@531d72ca), Student(id=1002, name=Lucy, gender=serialize.ktxSerialization.Female@22d8cfe0), Student(id=1003, name=HanMeimei, gender=serialize.ktxSerialization.Female@22d8cfe0), Student(id=1004, name=LiLei, gender=serialize.ktxSerialization.Male@531d72ca)]
jsonString: [{"id":1001,"name":"Jimy","gender":{"type":"serialize.ktxSerialization.Male"}},{"id":1002,"name":"Lucy","gender":{"type":"serialize.ktxSerialization.Female"}},{"id":1003,"name":"HanMeimei","gender":{"type":"serialize.ktxSerialization.Female"}},{"id":1004,"name":"LiLei","gender":{"type":"serialize.ktxSerialization.Male"}}]
list2: [Student(id=1001, name=Jimy, gender=serialize.ktxSerialization.Male@531d72ca), Student(id=1002, name=Lucy, gender=serialize.ktxSerialization.Female@22d8cfe0), Student(id=1003, name=HanMeimei, gender=serialize.ktxSerialization.Female@22d8cfe0), Student(id=1004, name=LiLei, gender=serialize.ktxSerialization.Male@531d72ca)]

这里很好理解:
在没有给 Gson 注册 TypeAdapter 的时候,使用默认的 TypeAdapter, 把引用类型序列化为了空。反序列化时才会失败。而是用 kotlinx.serialization ,相当于默认提供了一个序列化和反序列方案。所以直接可以成功。无需我们自己定义序列化和反序列化的规则。

四、总结

最后对本文做个总结:

  • 在使用 Gson 进行序列化和反序列过程中。要注意多态的情况下。需要自己注册 TypeAdapter。
  • 如果使用 Kotlin 开发,优先使用高效的序列化框架:kotlinx.serialization

kotlinx.serialization 具有如下特性:

  1. 类型安全:满足 Kotlin 的强制类型安全。可处理 Kotlin 的可空类型。
  2. 支持属性默认值:解析 JSON 的时候支持 Kotlin 类中属性的默认值。
  3. 支持泛型类型:API在序列化和反序列化泛型类型的时候非常简单也非常高效。
  4. 序列化字段名:当 json 的 key 和字段名不一致时,可以通过 @SerialName 给字段进行序列化。 同 Gson 中的 @SerializedName
  5. 序列化引用对象:当属性的类型是引用类型时,对该类型也需要使用 @Serializable 注解。
  6. 数据校验:可以再 json 反序列化时对数据进行校验。
  7. 支持 Retrofit 库。详见针对Retrofit 2 Converter.Factory的Kotlin序列化的库。

kotlinx.serialization 有很多优秀的特性。本文算是抛砖引玉。更多特性,请自己手动 Coding 体验。
最后附上 kotlinx.serialization 的官方文档:https://github.com/Kotlin/kotlinx.serialization文章来源地址https://www.toymoban.com/news/detail-750563.html

到了这里,关于高效的 Json 解析框架 kotlinx.serialization的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何在pytest接口自动化框架中扩展JSON数据解析功能?

    上期内容简单说到了。params类类型参数的解析方法。相较于简单。本期内容就json格式的数据解析,来进行阐述。 在MeterSphere中,有两种方式可以进行json格式的数据维护。一种是使用他们自带的JsonSchema来填写key-value表单。另一种就是手写json。 手写json在日常工作中效率较低,

    2024年02月11日
    浏览(50)
  • 关于前后端JSON解析差异问题与思考

      一、问题回顾 在一次涉及流程表单的需求发布时,由于表单设计的改动,需要在历史工单中的一个json字段增加一个属性,效果示意如下: 由于历史数据较多,采用了通过odc从数据库查询数据,线下开发数据处理脚本,更新数据后生成sql去线上执行,脚本示例如下。 在数据

    2024年02月09日
    浏览(50)
  • 《深度解析》打金工作室是如何形成的,又如何高效解决此类黑产问题?

    游戏黑产 以下的场景,对于游戏爱好者来说,一定不陌生。 玩游戏时常会碰到一些游戏商人,用低于官方的价格出售游戏资源,运气好的可能还真碰上货真价实的,运气不好,很可能就人财两空。 对玩家来说前期看似获得了好处,久而久之,大量的黑产帐号会挤压正常玩家

    2024年02月16日
    浏览(59)
  • 前端异步请求并解决跨域问题(Ajax+axios框架)、后端响应多个数据(JSON)

    目录 一、前后端同步异步请求 1.同步请求: 2.异步请求: 3.跨域问题(前端问题) 4.axios框架(封装后) 二、后端向前端响应多个数据-JSON 1.同步请求:         发送一个请求,回应请求,回应的内容会覆盖浏览器中的内容,这样会 打断 前端其他的正常操作。 2.异步请求:

    2024年02月07日
    浏览(68)
  • PHP解析带BOM头的JSON数据,对接他人接口的时候,使用json_decode(),返回null的问题与解决方法

    在php开发中会遇到和他人对接接口,对方使用json传输数据,使用json_decode()函数却无法将json数据转换为数组。 先看封装的代码 这种对接post接口的封装方法一般是没有问题的,但是我们打印$res的时候,会发现只会返回NULL。而打印$response的时候,是可以返回json字符串的。我们

    2024年02月06日
    浏览(46)
  • Kotlin 协程库v1.7.1的核心模块(kotlinx-coroutines-core)-- kotlinx.coroutines篇

    目录 asContextElement: asCoroutineDispatcher: js asDeferred: asExecutor: js asPromise: async: js await: awaitAll: awaitCancellation: cancelAndJoin: cancelChildren: CancellableContinuation: CancellationException: CloseableCoroutineDispatcher: CompletableDeferred: CompletableJob:  completeWith:  CompletionHandler: CoroutineDis

    2024年02月03日
    浏览(42)
  • 判断是否是json字符串

    一、在isJson.js文件里创建一个isJson类并抛出 二、使用 2.1、在需要的文件中引入isJson 2.2、声明类 2.3、调用

    2024年01月24日
    浏览(76)
  • 判断平面中两射线是否相交的高效方法

    最近在工作中遇到判断平面内两射线是否相交的问题。 对于这个问题的解决,常规的方法是将两条射线拓展为直线,计算直线的交点,而后判断交点是否在射线上。 这种方法,在思路上较为直观,也易于理解。然后,该方法在计算量上相对较大。对于少量射线间的交点计算

    2024年02月12日
    浏览(47)
  • 判断一个数据是否为 JSON 数据与使用场景

    判断一个数据是否为 JSON 数据,可以通过以下几个步骤: 首先,判断该数据是否为字符串类型。因为 JSON 数据通常是以字符串形式传输的。 然后,使用 try...catch 语句尝试将该字符串解析为 JSON 数据。如果解析成功,则说明该数据为 JSON 数据;否则,说明该数据不是 JSON 数据

    2024年02月09日
    浏览(50)
  • PostgreSQL 查询json/jsonb是否存在某个片段

    在PostgreSQL中,jsonb有额外的操作符,如 @、@、?、?|、? 可以用来查询是否包含路径/值,以及顶层键值是否存在。 详细文章:PostgreSQL 操作json/jsonb 那么,如果我们不知道路径,只想要查询json/jsonb是否存在某个片段,那就跟我一起来学习吧! 思路:使用 ::text 将json/jsonb转换成为

    2024年02月15日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包