用 Moshi 序列化 sealed class
Serialize sealed class with Moshi
以下将产生一个 IllegalArgumentException 因为你 "Cannot serialize abstract class"
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
}
}
我很高兴必须使用自定义适配器来编写每种类型的名称,但我需要一个解决方案来自动序列化每种类型的属性,而不必手动编写它们。
我认为您需要多态适配器来实现此目的,这需要 moshi-adapters 工件。这将启用具有不同属性的 sealed 类 的序列化。更多详细信息在本文中:https://proandroiddev.com/moshi-polymorphic-adapter-is-d25deebbd7c5
我通过创建一个工厂、一个封闭的 class 和一个可以为每个项目类型提供 classes 的枚举解决了这个问题。然而,这感觉相当笨拙,我希望有一个更直接的解决方案。
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()
}
}
}
}
您应该能够创建自己的 JsonAdapter.Factory
并在需要 Animal
时提供自定义适配器 serialized/deserialized:
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)
}
这可以通过 PolymorphicJsonAdapterFactory
并在 json 中包含一个额外的 属性 来指定类型。
例如:
这个JSON
{
"animals": [
{
"type": "dog",
"isGoodBoy": true
},
{
"type": "cat",
"remainingLives": 9
}
]
}
可以映射到下面类
sealed class Animal {
@JsonClass(generateAdapter = true)
data class Dog(val isGoodBoy: Boolean) : Animal()
@JsonClass(generateAdapter = true)
data class Cat(val remainingLives: Int) : Animal()
object Unknown : Animal()
}
使用以下 Moshi 配置
Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Animal::class.java, "type")
.withSubtype(Animal.Dog::class.java, "dog")
.withSubtype(Animal.Cat::class.java, "cat")
.withDefaultValue(Animal.Unknown)
)
以下将产生一个 IllegalArgumentException 因为你 "Cannot serialize abstract class"
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
}
}
我很高兴必须使用自定义适配器来编写每种类型的名称,但我需要一个解决方案来自动序列化每种类型的属性,而不必手动编写它们。
我认为您需要多态适配器来实现此目的,这需要 moshi-adapters 工件。这将启用具有不同属性的 sealed 类 的序列化。更多详细信息在本文中:https://proandroiddev.com/moshi-polymorphic-adapter-is-d25deebbd7c5
我通过创建一个工厂、一个封闭的 class 和一个可以为每个项目类型提供 classes 的枚举解决了这个问题。然而,这感觉相当笨拙,我希望有一个更直接的解决方案。
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()
}
}
}
}
您应该能够创建自己的 JsonAdapter.Factory
并在需要 Animal
时提供自定义适配器 serialized/deserialized:
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)
}
这可以通过 PolymorphicJsonAdapterFactory
并在 json 中包含一个额外的 属性 来指定类型。
例如:
这个JSON
{
"animals": [
{
"type": "dog",
"isGoodBoy": true
},
{
"type": "cat",
"remainingLives": 9
}
]
}
可以映射到下面类
sealed class Animal {
@JsonClass(generateAdapter = true)
data class Dog(val isGoodBoy: Boolean) : Animal()
@JsonClass(generateAdapter = true)
data class Cat(val remainingLives: Int) : Animal()
object Unknown : Animal()
}
使用以下 Moshi 配置
Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Animal::class.java, "type")
.withSubtype(Animal.Dog::class.java, "dog")
.withSubtype(Animal.Cat::class.java, "cat")
.withDefaultValue(Animal.Unknown)
)