Skip to content

Instantly share code, notes, and snippets.

@krishnabhargav
Last active March 21, 2024 14:41
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save krishnabhargav/7b1832eeb86aa213ba5bb239153977ea to your computer and use it in GitHub Desktop.
Save krishnabhargav/7b1832eeb86aa213ba5bb239153977ea 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()
}
}
@krishnabhargav
Copy link
Author

krishnabhargav commented Sep 19, 2019

Output:

Running serialization
$
{"name":"Archana","salary":1233}
Running deserialization
D1(name=Krishna, age=34)
D2(name=Archana, salary=1233)
Write: SealedClassTypeAdapter
Write: SealedClassTypeAdapter
Write: SealedClassTypeAdapter
Json : {"p":{"D3":{"p":{"D3":{"p":{"D1":{"name":"Krishna","age":34}}}}}}}
L1 : Container(p=D3(p=D3(p=D1(name=Krishna, age=34))))
Are L1 Equal? : true
Write: SealedClassTypeAdapter
Write: SealedClassTypeAdapter
Write: SealedClassTypeAdapter
Write: SealedClassTypeAdapter
Json: {"c":{"Container":{"p":{"D3":{"p":{"D3":{"p":{"D1":{"name":"Krishna","age":34}}}}}}}}}
Write: SealedClassTypeAdapter
Json: {"c":{"Singleton":{}}}
Container match : true
Singleton container -> SealedClassTop$Singleton@7dc3712
Singleton match : true

@dinhthaidaica
Copy link

This solution works perfectly in debug mode, but with production-build where proguard is applied, it doesn't work.
Do you have any recommendation ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment