Moshi + Kotlin + SealedClass

半城伤御伤魂 提交于 2020-07-05 05:44:08

问题


Is there a way of deserializing json using

sealed class Layer

data class ShapeLayer(var type: LayerType) : Layer
data class TextLayer(var type: LayerType) : Layer
data class ImageLayer(var type: LayerType) : Layer

LayerType is just some enum which can be used to distinguish which type should this object have.

I thought I could add Adapter this way:

class LayerAdapter{
    @FromJson
    fun fromJson(layerJson: LayerJson): Layer {
        return when (layerJson.layerType) {
            LayerType.SHAPE -> PreCompLayer()
            LayerType.SOLID -> SolidLayer()
            LayerType.Text -> TextLayer()
        }
    }
}

Where LayerJson would be object which has every possible field of all LayerTypes.

Now the problem is:

Cannot serialize abstract class com.example.models.layers.Layer

I could try to use interface, but I don't think it would be correct to use empty interface in this.


回答1:


Yes, you can create a custom type adapter to parse json according to the layerType like this:

class LayerAdapter {
    @FromJson
    fun fromJson(layerJson: LayerJson): Layer = when (layerJson.layerType) {
        LayerType.SHAPE -> ShapeLayer(layerJson.layerType, layerJson.shape ?: "")
        LayerType.TEXT -> TextLayer(layerJson.layerType, layerJson.text ?: "")
        LayerType.IMAGE -> ImageLayer(layerJson.layerType, layerJson.image ?: "")
    }

    @ToJson
    fun toJson(layer: Layer): LayerJson = when (layer) {
        is ShapeLayer -> LayerJson(layer.type, shape = layer.shape)
        is TextLayer -> LayerJson(layer.type, text = layer.text)
        is ImageLayer -> LayerJson(layer.type, image = layer.image)
        else -> throw RuntimeException("Not support data type")
    }
}

Here I have make some changes to your data class for clarity (an extra property to each of the Layer type, e.g. shape for ShapeLayer):

sealed class Layer

data class ShapeLayer(val type: LayerType, val shape: String) : Layer()
data class TextLayer(val type: LayerType, val text: String) : Layer()
data class ImageLayer(val type: LayerType, val image: String) : Layer()

//LayerJson contains every possible property of all layers
data class LayerJson(val layerType: LayerType, val shape: String? = null, val text: String? = null, val image: String? = null) : Layer()

enum class LayerType {
    SHAPE, TEXT, IMAGE
}

Testing code:

val moshi = Moshi.Builder()
        .add(LayerAdapter())
        .build()
val type = Types.newParameterizedType(List::class.java, Layer::class.java)
val adapter = moshi.adapter<List<Layer>>(type)

//Convert from json string to List<Layer>
val layers: List<Layer>? = adapter.fromJson("""
    [
        {"layerType":"SHAPE", "shape":"I am rectangle"},
        {"layerType":"TEXT", "text":"I am text"},
        {"layerType":"IMAGE", "image":"I am image"}
    ]
""".trimIndent())
layers?.forEach(::println)

//Convert a list back to json string
val jsonString: String = adapter.toJson(layers)
println(jsonString)

Output:

ShapeLayer(type=SHAPE, shape=I am rectangle)
TextLayer(type=TEXT, text=I am text)
ImageLayer(type=IMAGE, image=I am image)
[{"layerType":"SHAPE","shape":"I am rectangle"},{"layerType":"TEXT","text":"I am text"},{"image":"I am image","layerType":"IMAGE"}]

Edit: You can add the adapter as usual when you are trying to parse other object which contain Layer. Suppose you have an object like this:

data class LayerContainer(val layers: List<Layer>)

Testing code:

val moshi = Moshi.Builder()
        .add(LayerAdapter())
        .build()

val adapter = moshi.adapter(LayerContainer::class.java)
val layerContainer: LayerContainer? = adapter.fromJson("""
    {
        "layers": [
            {"layerType":"SHAPE", "shape":"I am rectangle"},
            {"layerType":"TEXT", "text":"I am text"},
            {"layerType":"IMAGE", "image":"I am image"}
        ]
    }
""".trimIndent())
layerContainer?.layers?.forEach(::println)

val jsonString: String = adapter.toJson(layerContainer)
println(jsonString)



回答2:


It turned out that my code was actually correct from beginning!

Problem was with field declaration inside data Class:

data class LayerContainer(var/val layers: List<Layer>)

It works with val, and doesn't work with var! Kotlin somehow creates different code down below. This is my final code for this part of model:

@JvmSuppressWildcards var layers: List<Layer>


来源:https://stackoverflow.com/questions/46345022/moshi-kotlin-sealedclass

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!