Last active
October 28, 2024 20:10
-
-
Save krishnabhargav/7b1832eeb86aa213ba5bb239153977ea to your computer and use it in GitHub Desktop.
Using GSON to support serialization and deserialization of Kotlin Sealed Classes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
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
Output: