Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize/deserialize Kotlin sealed class?

I have a following sealed class:

sealed class ViewModel {

  data class Loaded(val value : String) : ViewModel()
  object Loading : ViewModel()

}

How can I serialize/deserialize instances of the ViewModel class, let's say to/from JSON format?

I've tried to use Genson serializer/deserializer library - it can handle Kotlin data classes, it's also possible to support polymorphic types (eg. using some metadata to specify concrete types).

However, the library fails on Kotlin object types, as these are singletons without a public constructor. I guess I could write a custom Genson converter to handle it, but maybe there's an easier way to do it?

like image 331
Zbigniew Malinowski Avatar asked May 03 '18 14:05

Zbigniew Malinowski


People also ask

Can a sealed class be serializable?

Sealed classes The most straightforward way to use serialization with a polymorphic hierarchy is to mark the base class sealed . All subclasses of a sealed class must be explicitly marked as @Serializable .

Can we inherit sealed class in Kotlin?

2. Sealed interfaces allow multiple inheritance. Just like what happens for standard interfaces, a Kotlin class can implement more than one sealed interface.

What is the use of sealed class in Kotlin?

Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.

What is serialization and deserialization in Kotlin?

Serialization is the process of converting an object into a sequence of bytes or a string, and deserialization is the process of rebuilding that sequence into a new object. There are several ways to persist an object in Kotlin.

How to define subclasses of sealed classes in Kotlin?

Note: All the subclasses of the sealed class must be defined within the same Kotlin file. However, it not necessary to define them within the sealed class, they can be defined in any scope where the sealed class is visible. // A sealed class with a single subclass defined inside sealed class ABC { class X: ABC () {...}

How do I serialize a JSON string in kotlinx?

The API is located in the the kotlinx.serialization package and its format-specific subpackages such as kotlinx.serialization.json. First, make a class serializable by annotating it with @Serializable. Copied! You can now serialize an instance of this class by calling Json.encodeToString ().

What serialization formats does kotlinx support?

kotlinx.serialization provides sets of libraries for all supported platforms – JVM, JavaScript, Native – and for various serialization formats – JSON, CBOR, protocol buffers, and others. You can find the complete list of supported serialization formats below.


2 Answers

I had a similar problem recently (although using Jackson, not Genson.)

Assuming I have the following:

sealed class Parent(val name: String)

object ChildOne : Parent("ValOne")
object ChildTwo : Parent("ValTwo")

Then adding a JsonCreator function to the sealed class:

sealed class Parent(val name: String) {

    private companion object {
        @JsonCreator
        @JvmStatic
        fun findBySimpleClassName(simpleName: String): Parent? {
            return Parent::class.sealedSubclasses.first {
                it.simpleName == simpleName
            }.objectInstance
        }
    }
}

Now you can deserialize using ChildOne or ChildTwo as key in your json property.

like image 106
SergioLeone Avatar answered Sep 18 '22 14:09

SergioLeone


You are probably right about the creating a custom serializer.

I have tried to serialize and de-serialize your class using the Jackson library and Kotlin.

These are the Maven dependencies for Jackson:

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.8.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.8</version>
</dependency>

You can serialize the sealed class to JSON using this library with no extra custom serializers, but de-serialization requires a custom de-serializer.

Below is the toy code I have used to serialize and de-serialize your sealed class:

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule

sealed class ViewModel {
    data class Loaded(val value: String) : ViewModel()
    object Loading : ViewModel()
}

// Custom serializer
class ViewModelDeserializer : JsonDeserializer<ViewModel>() {
    override fun deserialize(jp: JsonParser?, p1: DeserializationContext?): ViewModel {
        val node: JsonNode? = jp?.getCodec()?.readTree(jp)
        val value = node?.get("value")
        return if (value != null) ViewModel.Loaded(value.asText()) else ViewModel.Loading
    }
}

fun main(args: Array<String>) {
    val m = createCustomMapper()
    val ser1 = m.writeValueAsString(ViewModel.Loading)
    println(ser1)
    val ser2 = m.writeValueAsString(ViewModel.Loaded("test"))
    println(ser2)
    val deserialized1 = m.readValue(ser1, ViewModel::class.java)
    val deserialized2 = m.readValue(ser2, ViewModel::class.java)
    println(deserialized1)
    println(deserialized2)
}

// Using mapper with custom serializer
private fun createCustomMapper(): ObjectMapper {
    val m = ObjectMapper()
    val sm = SimpleModule()
    sm.addDeserializer(ViewModel::class.java, ViewModelDeserializer())
    m.registerModule(sm)
    return m
}

If you run this code this is the output:

{}
{"value":"test"}
ViewModel$Loading@1753acfe
Loaded(value=test)
like image 40
gil.fernandes Avatar answered Sep 19 '22 14:09

gil.fernandes