diff --git a/Troubleshooting.md b/Troubleshooting.md
index 525df0154e..e6f21034fe 100644
--- a/Troubleshooting.md
+++ b/Troubleshooting.md
@@ -313,6 +313,8 @@ Note: For newer Gson versions these rules might be applied automatically; make s
**Symptom:** A `JsonIOException` with the message 'Abstract classes can't be instantiated!' is thrown; the class mentioned in the exception message is not actually `abstract` in your source code, and you are using the code shrinking tool R8 (Android app builds normally have this configured by default).
+Note: If the class which you are trying to deserialize is actually abstract, then this exception is probably unrelated to R8 and you will have to implement a custom [`InstanceCreator`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/InstanceCreator.html) or [`TypeAdapter`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) which creates an instance of a non-abstract subclass of the class.
+
**Reason:** The code shrinking tool R8 performs optimizations where it removes the no-args constructor from a class and makes the class `abstract`. Due to this Gson cannot create an instance of the class.
**Solution:** Make sure the class has a no-args constructor, then adjust your R8 configuration file to keep the constructor of the class. For example:
@@ -324,6 +326,10 @@ Note: For newer Gson versions these rules might be applied automatically; make s
}
```
+You can also use `(...);` to keep all constructors of that class, but then you might actually rely on `sun.misc.Unsafe` on both JDK and Android to create classes without no-args constructor, see [`GsonBuilder.disableJdkUnsafe()`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#disableJdkUnsafe()) for more information.
+
For Android you can add this rule to the `proguard-rules.pro` file, see also the [Android documentation](https://developer.android.com/build/shrink-code#keep-code). In case the class name in the exception message is obfuscated, see the Android documentation about [retracing](https://developer.android.com/build/shrink-code#retracing).
-Note: If the class which you are trying to deserialize is actually abstract, then this exception is probably unrelated to R8 and you will have to implement a custom [`InstanceCreator`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/InstanceCreator.html) or [`TypeAdapter`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) which creates an instance of a non-abstract subclass of the class.
+For Android you can alternatively use the [`@Keep` annotation](https://developer.android.com/studio/write/annotations#keep) on the class or constructor you want to keep. That might be easier than having to maintain a custom R8 configuration.
+
+Note that the latest Gson versions (> 2.10.1) specify a default R8 configuration. If your class is a top-level class or is `static`, has a no-args constructor and its fields are annotated with Gson's [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html), you might not have to perform any additional R8 configuration.
diff --git a/examples/android-proguard-example/README.md b/examples/android-proguard-example/README.md
index 942477d71a..902960fdfa 100644
--- a/examples/android-proguard-example/README.md
+++ b/examples/android-proguard-example/README.md
@@ -12,6 +12,9 @@ details on how ProGuard can be configured.
The R8 code shrinker uses the same rule format as ProGuard, but there are differences between these two
tools. Have a look at R8's Compatibility FAQ, and especially at the [Gson section](https://r8.googlesource.com/r8/+/refs/heads/main/compatibility-faq.md#gson).
-Note that newer Gson versions apply some of the rules shown in `proguard.cfg` automatically by default,
+Note that the latest Gson versions (> 2.10.1) apply some of the rules shown in `proguard.cfg` automatically by default,
see the file [`gson/META-INF/proguard/gson.pro`](/gson/src/main/resources/META-INF/proguard/gson.pro) for
-the Gson version you are using.
+the Gson version you are using. In general if your classes are top-level classes or are `static`, have a no-args constructor and their fields are annotated with Gson's [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html), you might not have to perform any additional ProGuard or R8 configuration.
+
+An alternative to writing custom keep rules for your classes in the ProGuard configuration can be to use
+Android's [`@Keep` annotation](https://developer.android.com/studio/write/annotations#keep).
diff --git a/gson/pom.xml b/gson/pom.xml
index 0e120d3e44..9a48ef4ded 100644
--- a/gson/pom.xml
+++ b/gson/pom.xml
@@ -62,7 +62,7 @@
com.google.guavaguava-testlib
- 32.0.1-jre
+ 32.1.1-jretest
diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java
index 217beebb6e..68eb7d718b 100644
--- a/gson/src/main/java/com/google/gson/GsonBuilder.java
+++ b/gson/src/main/java/com/google/gson/GsonBuilder.java
@@ -58,8 +58,7 @@
* use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its
* various configuration methods, and finally calling create.
*
- *
The following is an example shows how to use the {@code GsonBuilder} to construct a Gson
- * instance:
+ *
The following example shows how to use the {@code GsonBuilder} to construct a Gson instance:
*
*
* Gson gson = new GsonBuilder()
@@ -125,7 +124,7 @@ public GsonBuilder() {
* Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder
* has the same configuration as the previously built Gson instance.
*
- * @param gson the gson instance whose configuration should by applied to a new GsonBuilder.
+ * @param gson the gson instance whose configuration should be applied to a new GsonBuilder.
*/
GsonBuilder(Gson gson) {
this.excluder = gson.excluder;
@@ -279,7 +278,7 @@ public GsonBuilder serializeNulls() {
* {"x":2,"y":3}}.
*
*
Given the assumption above, a {@code Map} will be
- * serialize as an array of arrays (can be viewed as an entry set of pairs).
+ * serialized as an array of arrays (can be viewed as an entry set of pairs).
*
*
Below is an example of serializing complex types as JSON arrays:
*
{@code
@@ -601,7 +600,7 @@ public GsonBuilder setDateFormat(String pattern) {
}
/**
- * Configures Gson to to serialize {@code Date} objects according to the style value provided.
+ * Configures Gson to serialize {@code Date} objects according to the style value provided.
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format.
*
@@ -622,7 +621,7 @@ public GsonBuilder setDateFormat(int style) {
}
/**
- * Configures Gson to to serialize {@code Date} objects according to the style value provided.
+ * Configures Gson to serialize {@code Date} objects according to the style value provided.
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
* invocation will be used to decide the serialization format.
*
diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java
index 7d2dc9b622..0f488a9bb9 100644
--- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java
+++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java
@@ -76,14 +76,15 @@ static String checkInstantiable(Class> c) {
if (Modifier.isAbstract(modifiers)) {
// R8 performs aggressive optimizations where it removes the default constructor of a class
// and makes the class `abstract`; check for that here explicitly
- if (c.getDeclaredConstructors().length == 0) {
- return "Abstract classes can't be instantiated! Adjust the R8 configuration or register"
- + " an InstanceCreator or a TypeAdapter for this type. Class name: " + c.getName()
- + "\nSee " + TroubleshootingGuide.createUrl("r8-abstract-class");
- }
-
- return "Abstract classes can't be instantiated! Register an InstanceCreator"
- + " or a TypeAdapter for this type. Class name: " + c.getName();
+ /*
+ * Note: Ideally should only show this R8-specific message when it is clear that R8 was
+ * used (e.g. when `c.getDeclaredConstructors().length == 0`), but on Android where this
+ * issue with R8 occurs most, R8 seems to keep some constructors for some reason while
+ * still making the class abstract
+ */
+ return "Abstract classes can't be instantiated! Adjust the R8 configuration or register"
+ + " an InstanceCreator or a TypeAdapter for this type. Class name: " + c.getName()
+ + "\nSee " + TroubleshootingGuide.createUrl("r8-abstract-class");
}
return null;
}
diff --git a/gson/src/main/java/com/google/gson/reflect/TypeToken.java b/gson/src/main/java/com/google/gson/reflect/TypeToken.java
index 4a695666c8..3921a70b64 100644
--- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java
+++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java
@@ -337,9 +337,12 @@ public static TypeToken get(Class type) {
* As seen here the result is a {@code TypeToken>}; this method cannot provide any type safety,
* and care must be taken to pass in the correct number of type arguments.
*
+ *
If {@code rawType} is a non-generic class and no type arguments are provided, this method
+ * simply delegates to {@link #get(Class)} and creates a {@code TypeToken(Class)}.
+ *
* @throws IllegalArgumentException
- * If {@code rawType} is not of type {@code Class}, if it is not a generic type, or if the
- * type arguments are invalid for the raw type
+ * If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for
+ * the raw type
*/
public static TypeToken> getParameterized(Type rawType, Type... typeArguments) {
Objects.requireNonNull(rawType);
@@ -354,10 +357,16 @@ public static TypeToken> getParameterized(Type rawType, Type... typeArguments)
Class> rawClass = (Class>) rawType;
TypeVariable>[] typeVariables = rawClass.getTypeParameters();
- // Note: Does not check if owner type of rawType is generic because this factory method
- // does not support specifying owner type
- if (typeVariables.length == 0) {
- throw new IllegalArgumentException(rawClass.getName() + " is not a generic type");
+ int expectedArgsCount = typeVariables.length;
+ int actualArgsCount = typeArguments.length;
+ if (actualArgsCount != expectedArgsCount) {
+ throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount +
+ " type arguments, but got " + actualArgsCount);
+ }
+
+ // For legacy reasons create a TypeToken(Class) if the type is not generic
+ if (typeArguments.length == 0) {
+ return get(rawClass);
}
// Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl
@@ -366,13 +375,6 @@ public static TypeToken> getParameterized(Type rawType, Type... typeArguments)
+ " it requires specifying an owner type");
}
- int expectedArgsCount = typeVariables.length;
- int actualArgsCount = typeArguments.length;
- if (actualArgsCount != expectedArgsCount) {
- throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount +
- " type arguments, but got " + actualArgsCount);
- }
-
for (int i = 0; i < expectedArgsCount; i++) {
Type typeArgument = Objects.requireNonNull(typeArguments[i], "Type argument must not be null");
Class> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
diff --git a/gson/src/main/resources/META-INF/proguard/gson.pro b/gson/src/main/resources/META-INF/proguard/gson.pro
index c9f235e95b..59d3bb441d 100644
--- a/gson/src/main/resources/META-INF/proguard/gson.pro
+++ b/gson/src/main/resources/META-INF/proguard/gson.pro
@@ -13,7 +13,7 @@
# Keep Gson annotations
# Note: Cannot perform finer selection here to only cover Gson annotations, see also https://stackoverflow.com/q/47515093
--keepattributes *Annotation*
+-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
### The following rules are needed for R8 in "full mode" which only adheres to `-keepattribtues` if
@@ -24,10 +24,10 @@
-keep class com.google.gson.reflect.TypeToken { *; }
# Keep any (anonymous) classes extending TypeToken
--keep class * extends com.google.gson.reflect.TypeToken
+-keep,allowobfuscation class * extends com.google.gson.reflect.TypeToken
# Keep classes with @JsonAdapter annotation
--keep @com.google.gson.annotations.JsonAdapter class *
+-keep,allowobfuscation,allowoptimization @com.google.gson.annotations.JsonAdapter class *
# Keep fields with @SerializedName annotation, but allow obfuscation of their names
-keepclassmembers,allowobfuscation class * {
@@ -35,7 +35,9 @@
}
# Keep fields with any other Gson annotation
--keepclassmembers class * {
+# Also allow obfuscation, assuming that users will additionally use @SerializedName or
+# other means to preserve the field names
+-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.Expose ;
@com.google.gson.annotations.JsonAdapter ;
@com.google.gson.annotations.Since ;
@@ -44,15 +46,25 @@
# Keep no-args constructor of classes which can be used with @JsonAdapter
# By default their no-args constructor is invoked to create an adapter instance
--keep class * extends com.google.gson.TypeAdapter {
+-keepclassmembers class * extends com.google.gson.TypeAdapter {
();
}
--keep class * implements com.google.gson.TypeAdapterFactory {
+-keepclassmembers class * implements com.google.gson.TypeAdapterFactory {
();
}
--keep class * implements com.google.gson.JsonSerializer {
+-keepclassmembers class * implements com.google.gson.JsonSerializer {
();
}
--keep class * implements com.google.gson.JsonDeserializer {
+-keepclassmembers class * implements com.google.gson.JsonDeserializer {
();
}
+
+# If a class is used in some way by the application, and has fields annotated with @SerializedName
+# and a no-args constructor, keep those fields and the constructor
+# Based on https://issuetracker.google.com/issues/150189783#comment11
+# See also https://github.com/google/gson/pull/2420#discussion_r1241813541 for a more detailed explanation
+-if class *
+-keepclasseswithmembers,allowobfuscation,allowoptimization class <1> {
+ ();
+ @com.google.gson.annotations.SerializedName ;
+}
diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java
index 9929c0495b..a8e8e88b98 100644
--- a/gson/src/test/java/com/google/gson/GsonTest.java
+++ b/gson/src/test/java/com/google/gson/GsonTest.java
@@ -17,7 +17,7 @@
package com.google.gson;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
import com.google.gson.Gson.FutureTypeAdapter;
import com.google.gson.internal.Excluder;
@@ -109,12 +109,8 @@ private static final class TestTypeAdapter extends TypeAdapter