用 Moshi 序列化密封类

Posted

技术标签:

【中文标题】用 Moshi 序列化密封类【英文标题】:Serialize sealed class with Moshi 【发布时间】:2019-09-19 21:21:03 【问题描述】:

以下将产生 IllegalArgumentException 因为您“无法序列化抽象类”

sealed class Animal 
    data class Dog(val isGoodBoy: Boolean) : Animal()
    data class Cat(val remainingLives: Int) : Animal()


private val moshi = Moshi.Builder()
    .build()

@Test
fun test() 
    val animal: Animal = Animal.Dog(true)
    println(moshi.adapter(Animal::class.java).toJson(animal))

我尝试使用自定义适配器解决此问题,但我能想到的唯一解决方案是为每个子类显式编写所有属性名称。例如:

class AnimalAdapter 
    @ToJson
    fun toJson(jsonWriter: JsonWriter, animal: Animal) 
        jsonWriter.beginObject()
        jsonWriter.name("type")
        when (animal) 
            is Animal.Dog -> jsonWriter.value("dog")
            is Animal.Cat -> jsonWriter.value("cat")
        

        jsonWriter.name("properties").beginObject()
        when (animal) 
            is Animal.Dog -> jsonWriter.name("isGoodBoy").value(animal.isGoodBoy)
            is Animal.Cat -> jsonWriter.name("remainingLives").value(animal.remainingLives)
        
        jsonWriter.endObject().endObject()
    

    ....

最终我希望生成如下所示的 JSON:


    "type" : "cat",
    "properties" : 
        "remainingLives" : 6
    


    "type" : "dog",
    "properties" : 
        "isGoodBoy" : true
    

我很高兴必须使用自定义适配器来编写每种类型的名称,但我需要一种能够自动序列化每种类型的属性的解决方案,而不必手动编写它们。

【问题讨论】:

【参考方案1】:

我认为您需要多态适配器来实现这一点,这需要 moshi-adapters 工件。这将启用具有不同属性的密封类的序列化。更多细节在这篇文章中:https://proandroiddev.com/moshi-polymorphic-adapter-is-d25deebbd7c5

【讨论】:

不幸的是,这个适配器设置了对象内部的“类型”。我在 Moshi repo 上发现了一个已关闭的问题,进一步解释了它:github.com/square/moshi/issues/813 显然我想要做的是“带有外部描述符的多态性”【参考方案2】:

我通过创建一个工厂、一个封闭类和一个可以为每个项目类型提供类的枚举来解决这个问题。然而,这感觉相当笨拙,我希望有一个更直接的解决方案。

data class AnimalObject(val type: AnimalType, val properties: Animal)

enum class AnimalType(val derivedClass: Class<out Animal>) 
    DOG(Animal.Dog::class.java),
    CAT(Animal.Cat::class.java)


class AnimalFactory : JsonAdapter.Factory 
    override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<AnimalObject>? 
        if (!Types.getRawType(type).isAssignableFrom(AnimalObject::class.java)) 
            return null
        

        return object : JsonAdapter<AnimalObject>() 
            private val animalTypeAdapter = moshi.adapter<AnimalType>(AnimalType::class.java)

            override fun fromJson(reader: JsonReader): AnimalObject? 
                TODO()
            

            override fun toJson(writer: JsonWriter, value: AnimalObject?) 
                writer.beginObject()
                writer.name("type")
                animalTypeAdapter.toJson(writer, value!!.type)
                writer.name("properties")
                moshi.adapter<Animal>(value.type.derivedClass).toJson(writer, value.properties)
                writer.endObject()
            
        
    

答案来自:github.com/square/moshi/issues/813

【讨论】:

【参考方案3】:

您应该能够创建自己的 JsonAdapter.Factory 并在需要序列化/反序列化 Animal 时提供自定义适配器:

sealed class Animal 
    @JsonClass(generateAdapter = true)
    data class Dog(val isGoodBoy: Boolean) : Animal()

    @JsonClass(generateAdapter = true)
    data class Cat(val remainingLives: Int) : Animal()


object AnimalAdapterFactory : JsonAdapter.Factory 
    override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi): JsonAdapter<*>? =
        when (type) 
            Animal::class.java -> AnimalAdapter(moshi)
            else -> null
        

    private class AnimalAdapter(moshi: Moshi) : JsonAdapter<Animal>() 

        private val mapAdapter: JsonAdapter<MutableMap<String, Any?>> =
            moshi.adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java))
        private val dogAdapter = moshi.adapter(Animal.Dog::class.java)
        private val catAdapter = moshi.adapter(Animal.Cat::class.java)

        override fun fromJson(reader: JsonReader): Animal? 
            val mapValues = mapAdapter.fromJson(reader)
            val type = mapValues?.get("type") ?: throw Util.missingProperty("type", "type", reader)
            val properties = mapValues["properties"] ?: throw Util.missingProperty("properties", "properties", reader)
            return when (type) 
                "dog" -> dogAdapter.fromJsonValue(properties)
                "cat" -> catAdapter.fromJsonValue(properties)
                else -> null
            
        

        override fun toJson(writer: JsonWriter, value: Animal?) 
            writer.beginObject()
            writer.name("type")
            when (value) 
                is Animal.Dog -> writer.value("dog")
                is Animal.Cat -> writer.value("cat")
            

            writer.name("properties")
            when (value) 
                is Animal.Dog -> dogAdapter.toJson(writer, value)
                is Animal.Cat -> catAdapter.toJson(writer, value)
            
            writer.endObject()
        
    


private val moshi = Moshi.Builder()
    .add(AnimalAdapterFactory)
    .build()

@Test
fun test() 
    val dog: Animal = Animal.Dog(true)
    val cat: Animal = Animal.Cat(7)
    println(moshi.adapter(Animal::class.java).toJson(dog))
    println(moshi.adapter(Animal::class.java).toJson(cat))
    val shouldBeDog: Animal? = moshi.adapter(Animal::class.java).fromJson(moshi.adapter(Animal::class.java).toJson(dog))
    val shouldBeCat: Animal? = moshi.adapter(Animal::class.java).fromJson(moshi.adapter(Animal::class.java).toJson(cat))
    println(shouldBeDog)
    println(shouldBeCat)

【讨论】:

我真的很喜欢这个解决方案,因为您可以使用 Moshi 的内部适配器,无论您使用的是 Codegen 还是 Reflection。我认为这是最简单的解决方案。

以上是关于用 Moshi 序列化密封类的主要内容,如果未能解决你的问题,请参考以下文章

像@jsonunwrapped一样的moshi序列化支持

如何使用 kotlinx 序列化使用 open val 序列化 kotlin 密封类

如何在Scala中使用Json4s序列化密封的抽象类?

(13)C#传智:访问修饰符,简单工厂模式,序列化与反序列化,部分类,密封类,接口(第13天)

Moshi 1.9.x 无法序列化 Kotlin 类型

在 moshi 中使用自定义 MapAdapter 序列化地图