sealed class serialisation

This commit is contained in:
AdrianLxM 2021-03-27 21:14:56 +01:00
parent 2c5725717c
commit fa538f059c
4 changed files with 114 additions and 4 deletions

View file

@ -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 <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()
/*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<XXXValueWithUnit>): String =
list.map { Container(it) }.let { generateGson().toJson(it) }
fun fromJson(string: String) : List<XXXValueWithUnit> {
val itemType = object : TypeToken<List<Container>>() {}.type
return generateGson().fromJson<List<Container>>(string, itemType).map { it.wrapped }
}
data class Container(val wrapped: XXXValueWithUnit)
class SealedClassTypeAdapter<T : Any>(private 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()
val name = value.javaClass.canonicalName
out.name(name.splitToSequence(".").last()).jsonValue(jsonString)
out.endObject()
}
}

View file

@ -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>(
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<XXXValueWithUnit>()
val serialized = toSealedClassJson(list)
val deserialized = fromJson(serialized)
assertEquals(0, list.size)
assertEquals(list, deserialized)
}
}

View file

@ -39,4 +39,5 @@ dependencies {
implementation "com.google.dagger:dagger-android:$dagger_version" implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version" implementation "com.google.dagger:dagger-android-support:$dagger_version"
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
} }

View file

@ -1,6 +1,7 @@
package info.nightscout.androidaps.database package info.nightscout.androidaps.database
import androidx.room.TypeConverter import androidx.room.TypeConverter
import com.google.gson.JsonArray
import info.nightscout.androidaps.database.data.Block import info.nightscout.androidaps.database.data.Block
import info.nightscout.androidaps.database.data.TargetBlock import info.nightscout.androidaps.database.data.TargetBlock
import info.nightscout.androidaps.database.embedments.InterfaceIDs import info.nightscout.androidaps.database.embedments.InterfaceIDs
@ -18,8 +19,7 @@ class Converters {
fun toAction(action: String?) = action?.let { Action.fromString(it) } fun toAction(action: String?) = action?.let { Action.fromString(it) }
@TypeConverter @TypeConverter
fun fromMutableListOfValueWithUnit(values: MutableList<ValueWithUnit>?): String? { fun fromMutableListOfValueWithUnit(values: MutableList<ValueWithUnit>): String {
if (values == null) return null
val jsonArray = JSONArray() val jsonArray = JSONArray()
values.forEach { values.forEach {
if (it.condition) { if (it.condition) {
@ -32,8 +32,7 @@ class Converters {
} }
@TypeConverter @TypeConverter
fun toMutableListOfValueWithUnit(jsonString: String?): MutableList<ValueWithUnit>? { fun toMutableListOfValueWithUnit(jsonString: String): MutableList<ValueWithUnit> {
if (jsonString == null) return null
val jsonArray = JSONArray(jsonString) val jsonArray = JSONArray(jsonString)
val list = mutableListOf<ValueWithUnit>() val list = mutableListOf<ValueWithUnit>()
for (i in 0 until jsonArray.length()) { for (i in 0 until jsonArray.length()) {