用 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 序列化密封类的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 kotlinx 序列化使用 open val 序列化 kotlin 密封类