如何序列化/反序列化Kotlin密封类?

问题描述 投票:0回答:5

我有以下密封课程:

sealed class ViewModel {

  data class Loaded(val value : String) : ViewModel()
  object Loading : ViewModel()

}

如何序列化/反序列化 ViewModel 类的实例,比如 JSON 格式?

我尝试使用 Genson 序列化器/反序列化器库 - 它可以处理 Kotlin 数据类,还可以支持多态类型(例如,使用一些元数据来指定具体类型)。

但是,该库在 Kotlin

object
类型上失败,因为这些是没有公共构造函数的单例。我想我可以编写一个自定义 Genson 转换器来处理它,但也许有更简单的方法来做到这一点?

json kotlin singleton deserialization genson
5个回答
8
投票

关于创建自定义序列化器,您可能是正确的。

我尝试使用 Jackson 库和 Kotlin 序列化和反序列化你的类。

这些是 Jackson 的 Maven 依赖项:

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.8</version>
</dependency>

您可以使用此库将密封类序列化为 JSON,无需额外的自定义序列化器,但反序列化需要自定义反序列化器。

下面是我用来序列化和反序列化密封类的玩具代码:

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule

sealed class ViewModel {
    data class Loaded(val value: String) : ViewModel()
    object Loading : ViewModel()
}

// Custom serializer
class ViewModelDeserializer : JsonDeserializer<ViewModel>() {
    override fun deserialize(jp: JsonParser?, p1: DeserializationContext?): ViewModel {
        val node: JsonNode? = jp?.getCodec()?.readTree(jp)
        val value = node?.get("value")
        return if (value != null) ViewModel.Loaded(value.asText()) else ViewModel.Loading
    }
}

fun main(args: Array<String>) {
    val m = createCustomMapper()
    val ser1 = m.writeValueAsString(ViewModel.Loading)
    println(ser1)
    val ser2 = m.writeValueAsString(ViewModel.Loaded("test"))
    println(ser2)
    val deserialized1 = m.readValue(ser1, ViewModel::class.java)
    val deserialized2 = m.readValue(ser2, ViewModel::class.java)
    println(deserialized1)
    println(deserialized2)
}

// Using mapper with custom serializer
private fun createCustomMapper(): ObjectMapper {
    val m = ObjectMapper()
    val sm = SimpleModule()
    sm.addDeserializer(ViewModel::class.java, ViewModelDeserializer())
    m.registerModule(sm)
    return m
}

如果运行此代码,这就是输出:

{}
{"value":"test"}
ViewModel$Loading@1753acfe
Loaded(value=test)

5
投票

我最近遇到了类似的问题(尽管使用的是 Jackson,而不是 Genson。)

假设我有以下内容:

sealed class Parent(val name: String)

object ChildOne : Parent("ValOne")
object ChildTwo : Parent("ValTwo")

然后在密封类中添加一个

JsonCreator
函数:

sealed class Parent(val name: String) {

    private companion object {
        @JsonCreator
        @JvmStatic
        fun findBySimpleClassName(simpleName: String): Parent? {
            return Parent::class.sealedSubclasses.first {
                it.simpleName == simpleName
            }.objectInstance
        }
    }
}

现在您可以在 json 属性中使用

ChildOne
ChildTwo
作为
key
进行反序列化。


3
投票

我最终实现了一个自定义转换器和一个工厂,以将其正确插入 Genson。

它使用 Genson 的元数据约定将对象表示为:

{ 
  "@class": "com.example.ViewModel.Loading" 
}

转换器假设设置了 useClassMetadata 标志,因此序列化只需要标记一个空对象。对于反序列化,它从元数据解析类名,加载它并获取objectInstance

object KotlinObjectConverter : Converter<Any> {

override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) {
    with(writer) {
        // just empty JSON object, class name will be automatically added as metadata
        beginObject()
        endObject()
    }
}

override fun deserialize(reader: ObjectReader, ctx: Context): Any? =
    Class.forName(reader.nextObjectMetadata().metadata("class"))
        .kotlin.objectInstance
        .also { reader.endObject() }
}

为了确保此转换器仅应用于实际的对象,我使用工厂注册它,它告诉 Genson 何时使用它以及何时回退到默认实现。

object KotlinConverterFactory : Factory<Converter<Any>> {

    override fun create(type: Type, genson: Genson): Converter<Any>? =
        if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter
        else null

}

工厂可用于通过构建器配置 Genson:

GensonBuilder()
        .withConverterFactory(KotlinConverterFactory)
        .useClassMetadata(true) // required to add metadata during serialization
        // some other properties
        .create()

使用链式转换器功能,代码可能会更好,但我还没有时间检查它。


3
投票

不需要

@JsonCreator
sealdSubClass
。 Jackson 在其
jackson-module-kotlin
中有此支持,只需要一个注释
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
:

  @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
  sealed class SuperClass{
      class A: SuperClass()
      class B: SuperClass()
  }

...
val mapper = jacksonObjectMapper()
val root: SuperClass = mapper.readValue(json)
when(root){
    is A -> "It's A"
    is B -> "It's B"
}

上面的示例是从其主存储库自述文件中复制的:https://github.com/FasterXML/jackson-module-kotlin


0
投票

我遇到了类似的问题,花了一天时间来解决它,@X.Y 的答案之一https://stackoverflow.com/a/71315804/18916677帮助了我,但它有点不完整

要解决您面临的问题,您需要两件事,

  1. 您的 Jackson

    objectMapper
    需要一个配置为支持 Singleton 对象的 KotlinModule,下面是代码片段

    val objectMapper: ObjectMapper = JsonMapper.builder().build() objectMapper.registerModule(KotlinModule.Builder() .enable(KotlinFeature.SingletonSupport).build())

  2. 第二件事是由上面的答案提供的 -> https://stackoverflow.com/a/71315804/18916677这是

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
    注释

通过执行上述两个步骤,Jackson 使用类名序列化对象,并使用相同的名称反序列化它,并保持单例行为完整

如果您仅通过第二步解决问题,您可能会发现问题已解决,但每次反序列化时最终都会有不同的实例

© www.soinside.com 2019 - 2024. All rights reserved.