Kotlin sealed class serialize

How to serialize/deserialize Kotlin sealed class?

I ended up implementing a custom Converter plus a Factory to properly plug it into Genson.

It uses Genson’s metadata convention to represent the object as:

The converter assumes useClassMetadata flag set, so serialization just needs to mark an empty object. For deserialization, it resolves class name from metadata, loads it and obtains objectInstance.

object KotlinObjectConverter : Converter  < override fun serialize(objectData: Any, writer: ObjectWriter, ctx: Context) < with(writer) < // just empty JSON object, class name will be automatically added as metadata beginObject() endObject() >> override fun deserialize(reader: ObjectReader, ctx: Context): Any? = Class.forName(reader.nextObjectMetadata().metadata("class")) .kotlin.objectInstance .also < reader.endObject() >> 

To make sure that this converter is applied only to actual objects, I register it using a factory, that tells Genson when to use it and when to fall back to the default implementation.

object KotlinConverterFactory : Factory> < override fun create(type: Type, genson: Genson): Converter? = if (TypeUtil.getRawClass(type).kotlin.objectInstance != null) KotlinObjectConverter else null > 

The factory can be used to configure Genson via builder:

GensonBuilder() .withConverterFactory(KotlinConverterFactory) .useClassMetadata(true) // required to add metadata during serialization // some other properties .create() 

The code probably could be even nicer with chained converters feature, but I didn’t have time to check it out yet.

Solution 2

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:

  com.fasterxml.jackson.core jackson-core 2.8.8   com.fasterxml.jackson.core jackson-databind 2.8.8  

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() < 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) < 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

If you run this code this is the output:

Читайте также:  What is arithmetic exception java

Solution 3

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.

Источник

Sealed Class Serializer

class SealedClassSerializer < T : Any > ( serialName : String , val baseClass : KClass < T > , subclasses : Array < KClass < out T > > , subclassSerializers : Array < KSerializer < out T > > ) : AbstractPolymorphicSerializer < T >

This class provides support for multiplatform polymorphic serialization of sealed classes.

In contrary to PolymorphicSerializer, all known subclasses with serializers must be passed in subclasses and subSerializers constructor parameters. If a subclass is a sealed class itself, all its subclasses are registered as well.

If a sealed hierarchy is marked with @Serializable, an instance of this class is provided automatically. In most of the cases, you won’t need to perform any manual setup:

@Serializable
sealed class SimpleSealed @Serializable
public data class SubSealedA(val s: String) : SimpleSealed()

@Serializable
public data class SubSealedB(val i: Int) : SimpleSealed()
>

// will perform correct polymorphic serialization and deserialization:
Json.encodeToString(SimpleSealed.serializer(), SubSealedA("foo"))

However, it is possible to register additional subclasses using regular SerializersModule. It is required when one of the subclasses is an abstract class itself:

@Serializable
sealed class ProtocolWithAbstractClass @Serializable
abstract class Message : ProtocolWithAbstractClass() @Serializable
data class StringMessage(val description: String, val message: String) : Message()

@Serializable
data class IntMessage(val description: String, val message: Int) : Message()
>

@Serializable
data class ErrorMessage(val error: String) : ProtocolWithAbstractClass()
>

In this case, ErrorMessage would be registered automatically by the plugin, but StringMessage and IntMessage require manual registration, as described in PolymorphicSerializer documentation:

val abstractContext = SerializersModule polymorphic(ProtocolWithAbstractClass::class) subclass(ProtocolWithAbstractClass.Message.IntMessage::class) 
subclass(ProtocolWithAbstractClass.Message.StringMessage::class)
// no need to register ProtocolWithAbstractClass.ErrorMessage
>
>

Источник

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

Читайте также:  Php session user agent

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to: serializing / deserializing sealed classes #103

How to: serializing / deserializing sealed classes #103

Comments

Let’s assume we have a simple (sealed) hierarchy of types

@Serializable sealed class A @Serializable data class B(val a : Int, val b: String) : A() @Serializable data class C(val c : Boolean, val d: Boolean) : B() 

Now we want to serialize instances (example: JSON):

fun toJSON( x : A ) < println( JSON.stringify(x) ) // == "<>" because A's serializer handles a class without fields > fun fromJSON(json: String) < return JSON.parse(json) // runtime error (?) > 

Of course this does not work, but how would I get something like this to work? Especially sharing between JVM and JS?

One solution is of course to build different data types via Composition instead of Inheritance, but I find this option to be something of a code smell in cases like this.

I’m somewhat reminded of Scala’s upickle, which is based on Macro based de-/serializers, with kotlinx.serialization doing something similar via preprocessing by compiler / build tool plugin (AFAIU). With sealed classes being compile time constants, this should definitly work in theory.

Is there a way to do this more elegantly, keeping the sealed class hierarchy intact?

The text was updated successfully, but these errors were encountered:

Sealed classes don’t have any kind of special support now, but usual PolymorphicSerializer should work fine for them.

On JS, I think it is possible to implement custom deserializer for base sealed class, using PolymorphicSerializer ‘s code as a reference; but instead of loading class for name and obtaining serializer do something like

val type = readString (...) // 1st element of array when(type) < "B" -> readSerializableElement(B.serializer(), ...) "C" -> readSerializableElement(C.serializer(),...) // others. >

I think when this feature would be supported by plugin, it would generate code similar to that

Читайте также:  Https erudit online ru quiz html

Tried something similar to @sandwwraith but was not successful perhaps I’m doing something foolish

override fun load(input: KInput): Item < val mapOfStrings = (StringSerializer to StringSerializer).map return input.read(mapOfStrings) .let < JSON.parse( CdItemTypes.valueOf(it["type"]!!).itemClass().serializer(), JSON.stringify(mapOfStrings, it) ) > >

«Any type is not supported»

@sandwwraith Can you shed more light on this, I am facing a similar issue, trying to serialize JSON for sealed classes and get the following error:

Expected ‘[, kind: POLYMORPHIC’

I have classes that look like this:

This is a version of PolymorphicSerializer which works in JS and JVM:

import kotlinx.serialization.KInput import kotlinx.serialization.KOutput import kotlinx.serialization.KSerialClassDesc import kotlinx.serialization.KSerialClassKind import kotlinx.serialization.KSerializer import kotlinx.serialization.SerializationException import kotlinx.serialization.internal.SerialClassDescImpl import kotlinx.serialization.serializer import kotlin.reflect.KClass fun T : Any> serializationModel(vararg serializables: KClassout T>) = SerializationModel(serializables) class SerializationModelT : Any>(val serializables: Arrayout KClassout T>>) class ModelSerializerT : Any>(private val model: SerializationModelT>) : KSerializer < override val serialClassDesc: KSerialClassDesc get() = PolymorphicClassDesc override fun save(output: KOutput, obj: T) < val saver = obj::class.serializer() as KSerializerT> @Suppress("NAME_SHADOWING") val output = output.writeBegin(serialClassDesc) output.writeIntElementValue(serialClassDesc, 0, model.serializables.indexOf(obj::class)) output.writeSerializableElementValue(serialClassDesc, 1, saver, obj) output.writeEnd(serialClassDesc) > override fun load(input: KInput): T < @Suppress("NAME_SHADOWING") val input = input.readBegin(serialClassDesc) var klassIndex: Int? = null var value: T? = null mainLoop@ while (true) < when (input.readElement(serialClassDesc)) < KInput.READ_ALL -> < klassIndex = input.readIntElementValue(serialClassDesc, 0) val loader = model.serializables[klassIndex].serializer() value = input.readSerializableElementValue(serialClassDesc, 1, loader) break@mainLoop > KInput.READ_DONE -> < break@mainLoop > 0 -> < klassIndex = input.readIntElementValue(serialClassDesc, 0) > 1 -> < klassIndex = requireNotNull(klassIndex) < "Cannot read polymorphic value before its type token" > val loader = model.serializables[klassIndex].serializer() value = input.readSerializableElementValue(serialClassDesc, 1, loader) > else -> throw SerializationException("Invalid index") > > input.readEnd(serialClassDesc) return requireNotNull(value) < "Polymorphic value have not been read" > > > internal object PolymorphicClassDesc : SerialClassDescImpl("kotlin.Any") < override val kind: KSerialClassKind = KSerialClassKind.POLYMORPHIC init < addElement("klass") addElement("value") > >

In your common code you need something like:

sealed class LoginRequestEvent < @Serializable data class ReuseSession(val sessionId: String) : LoginRequestEvent() @Serializable data class CreateSession(val username: String, val password: String) : LoginRequestEvent() > val model = serializationModel( LoginRequestEvent.ReuseSession::class, LoginRequestEvent.CreateSession::class )

You can use them equally in JVM and JS:

val serializer = ModelSerializer(model) // import model from common code val loginRequest = JSON.parseLoginRequestEvent>(serializer, payload) when(loginRequest) < is LoginRequestEvent.ReuseSession -> [...]

Источник

Оцените статью