Deserialize ImmutableList from kotlinx Collections with Gson

334 Views Asked by At

I am trying to deserialize an ImmutableList from the kotlinx.collections library using Gson in an Android project. For different project related reasons I can't use Kotlin serialization yet.

My custom JsonDeserializer for Gson looks like this:

class ImmutableListDeserializer : JsonDeserializer<ImmutableList<*>> {

    @Throws(JsonParseException::class)
    @Suppress("UnstableApiUsage")
    override fun deserialize(
        json: JsonElement,
        type: Type,
        context: JsonDeserializationContext
    ): ImmutableList<*> {
        val typeArguments = (type as ParameterizedType).actualTypeArguments
        val parameterizedType: Type = listOf<Any>(typeArguments[0]).type
        val list = context.deserialize<List<*>>(json, parameterizedType)
        return list.toImmutableList()
    }

    @Suppress("UnstableApiUsage", "UNCHECKED_CAST")
    private fun <E> listOf(arg: Type): TypeToken<List<E>> = object : TypeToken<List<E>>() {}
        .where(object : TypeParameter<E>() {}, TypeToken.of(arg) as TypeToken<E>)
}

and is added via .registerTypeAdapter(ImmutableList::class.java, ImmutableListDeserializer()) to the GsonBuilder.

This works fine as long as R8 isn't activated (e.g. debug variant). As soon as I use R8 (especially in full mode, which is the new default since AGP 8.0) I am seeing the following error when trying to call gson.fromJson(json, DataClassContainingImmutableList::class.java):

java.lang.ArrayIndexOutOfBoundsException: length=0; index=0
    at com.myapp.serialization.ImmutableListDeserializer.deserialize(ImmutableListDeserializer.kt:21)
    at com.myapp.serialization.ImmutableListDeserializer.deserialize(ImmutableListDeserializer.kt:11)
    at com.google.gson.internal.bind.TreeTypeAdapter.read(TreeTypeAdapter.java:76)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.readIntoField(ReflectiveTypeAdapterFactory.java:212)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$FieldReflectionAdapter.readField(ReflectiveTypeAdapterFactory.java:433)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:393)
    at com.google.gson.Gson.fromJson(Gson.java:1227)
    at com.google.gson.Gson.fromJson(Gson.java:1137)
    at com.google.gson.Gson.fromJson(Gson.java:1047)
    at com.google.gson.Gson.fromJson(Gson.java:982)

So the error is located in this line:

val parameterizedType: Type = listOf<Any>(typeArguments[0]).type

I added all the proguard / R8 rules found here: https://github.com/google/gson/blob/master/examples/android-proguard-example/proguard.cfg as well as some additional ones to also keep some Guava classes used in the custom deserializer:

-keep,allowobfuscation,allowshrinking class com.google.common.reflect.TypeToken { *; }
-keep,allowobfuscation,allowshrinking class * extends com.google.common.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class com.google.common.reflect.TypeParameter { *; }
-keep,allowobfuscation,allowshrinking class * extends com.google.common.reflect.TypeParameter

Can anyone tell me which rule might be missing causing the issue when running in release mode with R8?

1

There are 1 best solutions below

0
Dev4Life On

When you're applying the R8/proguard, it'll rename all your vars, classes and methods and so on. So what you can do is add required proguard rules which you've done already.

I checked your proguard file and I can say that you've provided enough rules, but what if the class you're deserializing is renamed or removed during the obfuscation?

So, you have to add proguard rules for that class as well. For example, if you're passing any model class, then you have to add proguard rules to keep that class intact or use @SerializedName("varName") annotation before cariable names to deny the obfuscation of the vars in that class.