From fa538f059c9c2616a115252d14b3b88dcef30ec3 Mon Sep 17 00:00:00 2001 From: AdrianLxM Date: Sat, 27 Mar 2021 21:14:56 +0100 Subject: [PATCH] sealed class serialisation --- .../androidaps/utils/SealedClassHelper.kt | 71 +++++++++++++++++++ .../utils/SealedClassHelperKtTest.kt | 39 ++++++++++ database/build.gradle | 1 + .../androidaps/database/Converters.kt | 7 +- 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/info/nightscout/androidaps/utils/SealedClassHelper.kt create mode 100644 core/src/test/java/info/nightscout/androidaps/utils/SealedClassHelperKtTest.kt diff --git a/core/src/main/java/info/nightscout/androidaps/utils/SealedClassHelper.kt b/core/src/main/java/info/nightscout/androidaps/utils/SealedClassHelper.kt new file mode 100644 index 0000000000..9b2477a02d --- /dev/null +++ b/core/src/main/java/info/nightscout/androidaps/utils/SealedClassHelper.kt @@ -0,0 +1,71 @@ +package info.nightscout.androidaps.utils + +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 info.nightscout.androidaps.database.entities.XXXValueWithUnit +import kotlin.jvm.internal.Reflection +import kotlin.reflect.KClass + +fun generateGson() = GsonBuilder().registerTypeAdapterFactory( + object : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter { + val kclass = Reflection.getOrCreateKotlinClass(type.rawType) + return if (kclass.sealedSubclasses.any()) { + SealedClassTypeAdapter(kclass, gson) + } else + gson.getDelegateAdapter(this, type) + } + }).create() + +/*fun testSealedClass(): XXXValueWithUnit.SimpleString? { + + val gson = generateGson() + val someValueWithUnit: Container = Container(XXXValueWithUnit.SimpleString("hello")) + val serialised = gson.toJson(someValueWithUnit) + + val deserialised : Container = gson.fromJson(serialised, Container::class.java) + + return deserialised.c as? XXXValueWithUnit.SimpleString + +}*/ + +fun toSealedClassJson(list: List): String = + list.map { Container(it) }.let { generateGson().toJson(it) } + +fun fromJson(string: String) : List { + val itemType = object : TypeToken>() {}.type + + return generateGson().fromJson>(string, itemType).map { it.wrapped } +} + +data class Container(val wrapped: XXXValueWithUnit) + +class SealedClassTypeAdapter(private val kclass: KClass, val gson: Gson) : TypeAdapter() { + + 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(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() + val name = value.javaClass.canonicalName + out.name(name.splitToSequence(".").last()).jsonValue(jsonString) + out.endObject() + } + +} \ No newline at end of file diff --git a/core/src/test/java/info/nightscout/androidaps/utils/SealedClassHelperKtTest.kt b/core/src/test/java/info/nightscout/androidaps/utils/SealedClassHelperKtTest.kt new file mode 100644 index 0000000000..0600a9478b --- /dev/null +++ b/core/src/test/java/info/nightscout/androidaps/utils/SealedClassHelperKtTest.kt @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.utils + +import info.nightscout.androidaps.database.entities.XXXValueWithUnit +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +class SealedClassHelperKtTest { + + + + @Test + fun testSerialisationDeserization() { + + val list = listOf( + XXXValueWithUnit.SimpleString("hello"), + XXXValueWithUnit.SimpleInt(5), + XXXValueWithUnit.UNKNOWN + ) + + val serialized = toSealedClassJson(list) + val deserialized = fromJson(serialized) + + assertEquals(3, list.size) + assertEquals(list, deserialized) + } + + @Test + fun testEmptyList() { + + val list = listOf() + + val serialized = toSealedClassJson(list) + val deserialized = fromJson(serialized) + + assertEquals(0, list.size) + assertEquals(list, deserialized) + } +} \ No newline at end of file diff --git a/database/build.gradle b/database/build.gradle index 0966d4b85e..d41e263136 100644 --- a/database/build.gradle +++ b/database/build.gradle @@ -39,4 +39,5 @@ dependencies { implementation "com.google.dagger:dagger-android:$dagger_version" implementation "com.google.dagger:dagger-android-support:$dagger_version" + api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } diff --git a/database/src/main/java/info/nightscout/androidaps/database/Converters.kt b/database/src/main/java/info/nightscout/androidaps/database/Converters.kt index 622846218a..3a89bc55d5 100644 --- a/database/src/main/java/info/nightscout/androidaps/database/Converters.kt +++ b/database/src/main/java/info/nightscout/androidaps/database/Converters.kt @@ -1,6 +1,7 @@ package info.nightscout.androidaps.database import androidx.room.TypeConverter +import com.google.gson.JsonArray import info.nightscout.androidaps.database.data.Block import info.nightscout.androidaps.database.data.TargetBlock import info.nightscout.androidaps.database.embedments.InterfaceIDs @@ -18,8 +19,7 @@ class Converters { fun toAction(action: String?) = action?.let { Action.fromString(it) } @TypeConverter - fun fromMutableListOfValueWithUnit(values: MutableList?): String? { - if (values == null) return null + fun fromMutableListOfValueWithUnit(values: MutableList): String { val jsonArray = JSONArray() values.forEach { if (it.condition) { @@ -32,8 +32,7 @@ class Converters { } @TypeConverter - fun toMutableListOfValueWithUnit(jsonString: String?): MutableList? { - if (jsonString == null) return null + fun toMutableListOfValueWithUnit(jsonString: String): MutableList { val jsonArray = JSONArray(jsonString) val list = mutableListOf() for (i in 0 until jsonArray.length()) {