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.
Here, we have a data class success object which contains the success Data and a data class Error object which contains the state of the error. This way, Sealed classes help us to write clean and concise code! That's all the information about Sealed classes and their usage in Kotlin.
Moshi is way faster than Gson(Link1, ) and uses less memory, This is due to its usage of Okio which can predict or expect ahead of the time the keys which helps on ignoring the unknown or unwanted fields while parsing a stream (A good article on this).
Kotlin has a great feature called sealed class, which allow us to extend an abstract class in a set of fixed concrete types defined in the same compilation unit (a file). In other words, is not possible to inherit from the abstract class without touching the file where it is defined.
note. enum classes can't extend a sealed class (as well as any other class), but they can implement sealed interfaces.
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)
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With