代码之家  ›  专栏  ›  技术社区  ›  Zbigniew Malinowski

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

  •  16
  • Zbigniew Malinowski  · 技术社区  · 7 年前

    我有以下密封类:

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

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

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

    但是,库在Kotlin上失败 object 类型,因为它们是没有公共构造函数的单例。我想我可以编写一个定制的Genson转换器来处理它,但也许有一种更简单的方法来处理它?

    4 回复  |  直到 7 年前
        1
  •  8
  •   gil.fernandes    7 年前

    关于创建自定义序列化程序,您可能是对的。

    我尝试使用 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)
    
        2
  •  5
  •   SergioLeone    6 年前

    我最近也遇到了类似的问题(虽然使用的是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
            }
        }
    }
    

    现在可以使用 ChildOne ChildTwo key 在json属性中。

        3
  •  3
  •   Zbigniew Malinowski    7 年前

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

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

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

    转换器假定 useClassMetadata 标志集,所以序列化只需要标记一个空对象。对于反序列化,它从元数据解析类名,加载它并获取 反对意见

    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()
    

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

        4
  •  1
  •   X.Y.    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"
    }
    

    上述示例摘自its主回购自述: https://github.com/FasterXML/jackson-module-kotlin