2017-09-21 1 views
5

est-il un moyen de désérialisation JSON en utilisantMoshi + Kotlin + SealedClass

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 est juste une ENUM peut être utilisé pour distinguer quel type doit avoir cet objet.

Je pensais que je pourrais ajouter Adapter cette façon:

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

Où LayerJson serait l'objet qui a tous les domaines possibles de tous LayerTypes.

Maintenant, le problème est:

Impossible de sérialiser classe abstraite com.example.models.layers.Layer

Je pourrais essayer d'utiliser l'interface, mais je ne pense pas que ce serait correct d'utiliser une interface vide dans ce.

+0

Je pense que vous venez de manquer la méthode '@ ToJson'? La réponse ci-dessous est correcte. –

+0

Non @ToJson est là, je viens de passer tout le code que je pensais ne pas être crucial par exemple – miszmaniac

+0

hm, alors, cela ressemble à la réponse. l'avez-vous fait fonctionner? –

Répondre

0

Il est apparu que mon code était réellement correct du début!

problème était avec la déclaration de champ à l'intérieur de classe de données:

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

Il fonctionne avec val, et ne fonctionne pas avec var! Kotlin crée en quelque sorte un code différent ci-dessous. Ceci est mon code final pour cette partie du modèle:

@JvmSuppressWildcards var layers: List<Layer> 
4

Oui, vous pouvez créer un adaptateur de type personnalisé pour analyser JSON selon la layerType comme ceci:

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

Ici, je dois faire quelques changements à votre classe de données pour plus de clarté (une propriété supplémentaire à chacun des le type Layer, par exemple shape pour 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 
} 

Code d'essai:

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) 

Sortie:

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: Vous pouvez ajouter l'adaptateur comme d'habitude lorsque vous essayez d'analyser tout autre objet qui contiennent Layer. Supposons que vous avez un objet comme celui-ci:

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

Code d'essai:

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) 
+4

Bonne réponse. Aime ça. –

+0

OMG c'est une réponse exhaustive :) Merci beaucoup! Malheureusement, cela ne fonctionne pas pour moi :) J'ai extrait mon json pour faire un tableau de couches à la racine de json, et cela fonctionne réellement, mais le problème est ailleurs: Mon json ressemble à ça: {"couches" : [... cette liste ...]} Je ne sais pas comment attacher cet adaptateur de liste typé au constructeur? – miszmaniac

+0

@miszmaniac J'ai mis à jour la réponse pour ce cas. – BakaWaii