【问题标题】:Serialize sealed class with Moshi用 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
    }
}

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

【问题讨论】:

    标签: android json kotlin moshi


    【解决方案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。我认为这是最简单的解决方案。
      猜你喜欢
      • 2022-07-22
      • 2020-11-20
      • 1970-01-01
      • 2020-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-21
      • 1970-01-01
      相关资源
      最近更新 更多