Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save danaimset/76f569d9c21e5ce734aa332f76bbd2e8 to your computer and use it in GitHub Desktop.
Save danaimset/76f569d9c21e5ce734aa332f76bbd2e8 to your computer and use it in GitHub Desktop.
Using GSON to support serialization and deserialization of Kotlin Sealed Classes.
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import kotlin.jvm.internal.Reflection
import kotlin.reflect.KClass
object Json {
val gson =
GsonBuilder().registerTypeAdapterFactory(
object : TypeAdapterFactory {
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {
val kclass = Reflection.getOrCreateKotlinClass(type.rawType)
return if (kclass.sealedSubclasses.any()) {
SealedClassTypeAdapter<T>(kclass, gson)
} else
gson.getDelegateAdapter(this, type)
}
}).create()
inline fun <reified T> fromJson(x: String): T = this.gson.fromJson(x, T::class.java)
fun <T> fromJsonWithClass(x: String, classObj: Class<T>): T =
this.serializer.fromJson(x, classObj)
inline fun <T> toJson(item: T): String = this.gson.toJson(item)
}
sealed class SealedClassOne {
data class D1(val name: String, val age: Int) : SealedClassOne()
data class D2(val name: String, val salary: Int) : SealedClassOne()
data class D3(val p: SealedClassOne) : SealedClassOne()
}
sealed class SealedClassTop {
data class Container(val p: SealedClassOne) : SealedClassTop()
object Singleton : SealedClassTop()
}
data class Container(val c: SealedClassTop)
fun main() {
val h1 = SealedClassOne.D1("Krishna", 34)
val h2 = SealedClassOne.D2("Archana", 1233)
println("Running serialization")
val h1Json = Json.toJson(h1)
println("JSON: $h1Json")
val h2Json = Json.toJson(h2)
println("JSON: $h2Json")
println("Running deserialization")
println(Json.fromJson<SealedClassOne.D1>(h1Json))
println(Json.fromJson<SealedClassOne.D2>(h2Json))
//super nested model!
val l1 = SealedClassTop.Container(SealedClassOne.D3(SealedClassOne.D3(h1)))
val l1Json = Json.toJson(l1)
println("Json : $l1Json")
val l1Back = Json.fromJson<SealedClassTop.Container>(l1Json)
println("L1 : $l1Back")
println("Are L1 Equal? : ${l1 == l1Back}")
//more complex shit
val container1 = Container(l1)
val container2 = Container(SealedClassTop.Singleton)
val container1Json = Json.toJson(container1)
println("Json: $container1Json")
val container2Json = Json.toJson(container2)
println("Json: $container2Json")
val container1Back = Json.fromJson<Container>(container1Json)
println("Container match : ${container1 == container1Back}")
val container2Back = Json.fromJson<Container>(container2Json)
println("Singleton container -> ${container2Back.c}")
println("Singleton match : ${container2 == container2Back}")
val container3Back = Json.fromJson<Container>(container2Json)
println("Extended! : $container3Back")
}
class SealedClassTypeAdapter<T : Any>(val kclass: KClass<Any>, val gson: Gson) : TypeAdapter<T>() {
override fun read(jsonReader: JsonReader): T? {
jsonReader.beginObject() //start reading the object
val nextName = jsonReader.nextName() //get the name on the object
val innerClass = kclass.sealedSubclasses.firstOrNull {
it.simpleName!!.contains(nextName)
} ?: throw Exception("$nextName is not found to be a data class of the sealed class ${kclass.qualifiedName}")
val x = gson.fromJson<T>(jsonReader, innerClass.javaObjectType)
jsonReader.endObject()
//if there a static object, actually return that back to ensure equality and such!
return innerClass.objectInstance as T? ?: x
}
override fun write(out: JsonWriter, value: T) {
val jsonString = gson.toJson(value)
out.beginObject()
out.name(value.javaClass.canonicalName.splitToSequence(".").last()).jsonValue(jsonString)
out.endObject()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment