web-dev-qa-db-ja.com

Moshi + Kotlin + SealedClass

を使用してjsonを逆シリアル化する方法はありますか

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は、このオブジェクトが持つべきタイプを区別するために使用できる列挙型です。

私はこの方法でアダプターを追加できると思いました:

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

LayerJsonは、すべてのLayerTypeのすべての可能なフィールドを持つオブジェクトになります。

今問題は:

抽象クラスcom.example.models.layers.Layerをシリアル化できません

インターフェイスを使用することはできますが、これで空のインターフェイスを使用するのは正しくないと思います。

13
miszmaniac

私のコードは最初から実際に正しいことがわかりました!

問題は、データクラス内のフィールド宣言にありました。

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

Valで動作し、varでは動作しません! Kotlinはどういうわけか以下に異なるコードを作成します。これは、モデルのこの部分の最終的なコードです。

@JvmSuppressWildcards var layers: List<Layer>
1
miszmaniac

はい、次のようにlayerTypeに従ってjsonを解析するカスタムタイプのアダプターを作成できます。

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")
    }
}

ここでは、わかりやすくするためにデータクラスにいくつかの変更を加えました(Layerタイプのそれぞれに追加のプロパティ。例:shapeの場合は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
}

テストコード:

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)

出力:

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"}]

編集:Layerを含む他のオブジェクトを解析しようとしているときは、通常どおりアダプターを追加できます。次のようなオブジェクトがあるとします。

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

テストコード:

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)
13
BakaWaii