80

My project implements a TypeAdapter in Gson during serialization/deserialization for preserving object's polymorphism state. Anyhow, the project works fine during development tests, but when it is released with proguard obfuscation and tested, it just crashes.

03-21 10:06:53.632: E/AndroidRuntime(12441): FATAL EXCEPTION: main
03-21 10:06:53.632: E/AndroidRuntime(12441): java.lang.AssertionError
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter.<init>(SourceFile:724)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.TypeAdapters$26.create(SourceFile:753)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.getAdapter(SourceFile:353)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(SourceFile:82)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(SourceFile:81)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(SourceFile:118)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(SourceFile:72)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.getAdapter(SourceFile:353)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.toJson(SourceFile:578)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.toJsonTree(SourceFile:479)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.toJsonTree(SourceFile:458)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson$3.serialize(SourceFile:137)

My Gson specific proguard configuration is:

##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

#This is extra - added by me to exclude gson obfuscation
-keep class com.google.gson.** { *; }

##---------------End: proguard configuration for Gson  ----------

The TypeAdapter I'm using is:

public final class GsonWorkshiftAdapter implements JsonSerializer<IWorkshift>, JsonDeserializer<IWorkshift> {
    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(IWorkshift src, Type typeOfSrc, JsonSerializationContext context) {
        String className = src.getClass().getCanonicalName();
        JsonElement elem = context.serialize(src);

        JsonObject retValue = new JsonObject();
        retValue.addProperty(CLASSNAME, className);
        retValue.add(INSTANCE, elem);

        return retValue;
    }

    @Override
    public IWorkshift deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject =  json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try { klass = Class.forName(className); }
        catch (ClassNotFoundException e) { throw new JsonParseException(e.getMessage()); }

        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

I did a lot of search on this error specific to Gson, but couldn't find any helpful answer. However I found another question with the similar issue.

Any help from developer's community would be appreciated.

Community
  • 1
  • 1
waqaslam
  • 67,549
  • 16
  • 165
  • 178
  • 1
    I wish I could upvote this question 100 times. I finally was able to resolve my app crashes in production, and at the same time learn a bit more about enums and proguard. Great question and thanks to everyone who posted very good answers with details. @Eric Lafortune – Brandon Mar 04 '18 at 17:09

7 Answers7

206

It seems like we must request that enumerations' members are kept. Adding this to the proguard config file worked for me:

-keepclassmembers enum * { *; }

Or, if you want to be more specific,

-keepclassmembers enum com.your.package.** { *; }
s-hunter
  • 24,172
  • 16
  • 88
  • 130
pandre
  • 6,685
  • 7
  • 42
  • 50
  • 4
    This should be the answer, it's referencing the exact exception. Thank you! – Daniel Wilson Jun 17 '16 at 22:55
  • 2
    Right answer, but could you kindly explain the reason? Thanks – Xiaogegexiao Dec 20 '17 at 00:54
  • The reason is that Gson's internal adapter for enums [iterates over the enum constants](https://github.com/google/gson/blob/ceae88b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java#L779) (this can also cause a different issue when Proguard renames `Enum.values()`) and uses their `name()` to then find the field and check for `@SerializedName`. However, Proguard renamed the fields so Gson fails finding them. `-keepclassmembers enum` prevents Proguard from renaming the fields. (Note that there is a [pull request](https://github.com/google/gson/pull/1495) for Gson as work around) – Marcono1234 Sep 09 '20 at 22:11
19

GSON throws this AssertionError when it fails to deserialize enumeration constants from JSON data, performing introspection on the fields of the enum class. Unfortunately, it swallows the details of the underlying NoSuchFieldException.

You should make sure that you preserve the names of the enum fields (and of fields in general) that are serialized. By default, ProGuard may rename or even remove them. For instance, with some wildcards:

-keepclassmembers class com.example.domain.** {
    <fields>;
}
Eric Lafortune
  • 45,150
  • 8
  • 114
  • 106
  • 9
    is `` here a placeholder for the names of the enum fields, or should it be written as-is? – Carlos P Jul 08 '14 at 16:13
  • 11
    As-is: , , , **, *, and ? are wildcards that ProGuard recognizes. – Eric Lafortune Jul 10 '14 at 19:14
  • 1
    I wish Gson would just have used a less presumptuous way to do the same thing. i.e., iterate the fields and get the name out, rather than assuming the name would match the field name at runtime. – Hakanai Jul 14 '17 at 05:52
15

It is already suggested that you need to configure Proguard in a way that it keeps every enum related to serialized objects intact. I don't really like the fact that I have to explicitly list all my enums, this solution is hard to maintain. A slightly better solution I came up with is the following.

Use an empty interface to indicate that a class or enum takes part in Gson serialization:

public interface GsonSerializable { }

public class MyClass implements GsonSerializable {

    public enum MyEnum implements GsonSerializable {
        enumvalue1, enumvalue2
    }

    public MyEnum mydata1;
}

Use a Proguard config that keeps both the interface and all classes/enums that implement it:

# keep GsonSerializable interface, it would be thrown away by proguard since it is empty
-keep class com.example.GsonSerializable

# member fields of serialized classes, including enums that implement this interface
-keepclassmembers class * implements com.example.GsonSerializable {
    <fields>;
}

# also keep names of these classes. not required, but just in case.
-keepnames class * implements com.example.GsonSerializable

That's it, as long as your classes and enums use the interface, you should be OK. You could also enforce the presence of this interface in your serialization/deserialization methods, so you don't forget it when adding a new class later:

public String serializeWithGson(GsonSerializable object) { ... }

Also in your configuration the line with 'com.google.gson.examples.android.model.** { *; }' refers to some Google related example code, so I don't think it is necessary.

Levente Dobson
  • 773
  • 8
  • 10
7

In my case, proguard was configured to -keep individual classes touched by Gson, but the error went away when I configured proguard to retain the package where those individual classes resided:

-keep class com.company.library.model.** { *; }
Rich Ehmer
  • 2,764
  • 23
  • 18
5

After running into the same problem, I went through and examined the resulting APK decompiled. I believe the problem is related to some enum type losing its members during obfuscation.

Be sure to keep enums:

 -keepclassmembers enum * {
     public static **[] values();
     public static ** valueOf(java.lang.String);
 }

Also - make sure ALL of the classes being used in GSON are being retained:

 -keep public class com.company.ordering.datacontract.** {
     public protected *;
 }

 -keep public class com.company.ordering.service.request.** {
     public protected *;
 }
 -keep public class com.company.ordering.service.response.** {
     public protected *;
 }

See full config @ pastebin.com/r5Jg3yY2

Eggman87
  • 571
  • 5
  • 12
1

Apply androidx.annotation.Keep annotation over the enum class. Like:

@Keep
enum class PlayerType {
    PRO,
    INTERMEDIATE,
    BASIC
}
Rahul Rastogi
  • 4,486
  • 5
  • 32
  • 51
0

Please verify following things-

  1. Is added proguard-rules.pro file added in app directory.

  2. is path defined in build.gradle(module:app) file path definition is correct like follows -

    proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

  3. If above 2 steps is okay then please add the following line(rule) in progaurd file-

    -keepclassmembers enum * { *; }

  4. Clean, build the project and run again.