Skip to content

Commit

Permalink
Merge branch 'main' into marcono1234/parameterized-type-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcono1234 committed Jun 23, 2023
2 parents 1f0de5e + 9cf0f8d commit fdc0d67
Show file tree
Hide file tree
Showing 50 changed files with 1,521 additions and 200 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ name: "CodeQL"

on:
push:
branches: [ master ]
branches: [ main ]
pull_request:
branches: [ master ]
branches: [ main ]
schedule:
# Run every Monday at 16:10
- cron: '10 16 * * 1'
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ _2015-10-04_
* New: APIs to add primitives directly to `JsonArray` instances.
* New: ISO 8601 date type adapter. Find this in _extras_.
* Fix: `FieldNamingPolicy` now works properly when running on a device with a Turkish locale.
[autovalue]: https://github.com/google/auto/tree/master/value
[autovalue]: https://github.com/google/auto/tree/main/value


## Version 2.3.1
Expand Down
48 changes: 47 additions & 1 deletion Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,53 @@ Gson prevents multiple fields with the same name because during deserialization
**Solution:** First check if you really need to serialize or deserialize a `Class`. Often it is possible to use string aliases and then map them to the known `Class`; you could write a custom [`TypeAdapter`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) to do this. If the `Class` values are not known in advance, try to introduce a common base class or interface for all these classes and then verify that the deserialized class is a subclass. For example assuming the base class is called `MyBaseClass`, your custom `TypeAdapter` should load the class like this:

```java
Class.forName(jsonString, false, getClass().getClassLoader()).asSubclass(MyBaseClass.cla‌​ss)
Class.forName(jsonString, false, getClass().getClassLoader()).asSubclass(MyBaseClass.class)
```

This will not initialize arbitrary classes, and it will throw a `ClassCastException` if the loaded class is not the same as or a subclass of `MyBaseClass`.

## <a id="type-token-raw"></a> `IllegalStateException`: 'TypeToken must be created with a type argument' <br> `RuntimeException`: 'Missing type parameter'

**Symptom:** An `IllegalStateException` with the message 'TypeToken must be created with a type argument' is thrown.
For older Gson versions a `RuntimeException` with message 'Missing type parameter' is thrown.

**Reason:**

- You created a `TypeToken` without type argument, for example `new TypeToken() {}` (note the missing `<...>`). You always have to provide the type argument, for example like this: `new TypeToken<List<String>>() {}`. Normally the compiler will also emit a 'raw types' warning when you forget the `<...>`.
- You are using a code shrinking tool such as ProGuard or R8 (Android app builds normally have this enabled by default) but have not configured it correctly for usage with Gson.

**Solution:** When you are using a code shrinking tool such as ProGuard or R8 you have to adjust your configuration to include the following rules:

```
# Keep generic signatures; needed for correct type resolution
-keepattributes Signature
# Keep class TypeToken (respectively its generic signature)
-keep class com.google.gson.reflect.TypeToken { *; }
# Keep any (anonymous) classes extending TypeToken
-keep class * extends com.google.gson.reflect.TypeToken
```

See also the [Android example](examples/android-proguard-example/README.md) for more information.

Note: For newer Gson versions these rules might be applied automatically; make sure you are using the latest Gson version and the latest version of the code shrinking tool.

## <a id="r8-abstract-class"></a> `JsonIOException`: 'Abstract classes can't be instantiated!' (R8)

**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).

**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:

```
# Keep the no-args constructor of the deserialized class
-keepclassmembers class com.example.MyClass {
<init>();
}
```

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.
7 changes: 6 additions & 1 deletion examples/android-proguard-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ or remove them if they appear to be unused. This can cause issues for Gson which
access the fields of a class. It is necessary to configure ProGuard to make sure that Gson works correctly.

Also have a look at the [ProGuard manual](https://www.guardsquare.com/manual/configuration/usage#keepoverview)
for more details on how ProGuard can be configured.
and the [ProGuard Gson examples](https://www.guardsquare.com/manual/configuration/examples#gson) for more
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,
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.
1 change: 1 addition & 0 deletions extras/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
13 changes: 6 additions & 7 deletions gson/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<version>2.18.0</version>
<version>2.20.0</version>
</dependency>

<dependency>
Expand All @@ -57,13 +57,12 @@
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>31.1-jre</version>
<version>32.0.1-jre</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -84,7 +83,7 @@
<goal>filter-sources</goal>
</goals>
<configuration>
<sourceDirectory>${basedir}/src/main/java-templates</sourceDirectory>
<sourceDirectory>${project.basedir}/src/main/java-templates</sourceDirectory>
<outputDirectory>${project.build.directory}/generated-sources/java-templates</outputDirectory>
</configuration>
</execution>
Expand Down Expand Up @@ -136,7 +135,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.2</version>
<configuration>
<!-- Deny illegal access, this is required for ReflectionAccessTest -->
<!-- Requires Java >= 9; Important: In case future Java versions
Expand Down Expand Up @@ -192,7 +191,7 @@
<injar>test-classes-obfuscated-injar</injar>
<outjar>test-classes-obfuscated-outjar</outjar>
<inFilter>**/*.class</inFilter>
<proguardInclude>${basedir}/src/test/resources/testcases-proguard.conf</proguardInclude>
<proguardInclude>${project.basedir}/src/test/resources/testcases-proguard.conf</proguardInclude>
<libs>
<lib>${project.build.directory}/classes</lib>
<lib>${java.home}/jmods/java.base.jmod</lib>
Expand Down Expand Up @@ -246,7 +245,7 @@
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
<version>1.0.0.RC3</version>
<version>1.0.0.Final</version>
<executions>
<execution>
<id>add-module-info</id>
Expand Down
67 changes: 55 additions & 12 deletions gson/src/main/java/com/google/gson/FormattingStyle.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@
/**
* A class used to control what the serialization output looks like.
*
* <p>It currently defines the kind of newline to use, and the indent, but
* might add more in the future.</p>
* <p>It currently has the following configuration methods, but more methods
* might be added in the future:
* <ul>
* <li>{@link #withNewline(String)}
* <li>{@link #withIndent(String)}
* <li>{@link #withSpaceAfterSeparators(boolean)}
* </ul>
*
* @see GsonBuilder#setPrettyPrinting(FormattingStyle)
* @see GsonBuilder#setFormattingStyle(FormattingStyle)
* @see JsonWriter#setFormattingStyle(FormattingStyle)
* @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a>
*
Expand All @@ -34,15 +39,30 @@
public class FormattingStyle {
private final String newline;
private final String indent;
private final boolean spaceAfterSeparators;

/**
* The default pretty printing formatting style using {@code "\n"} as
* newline and two spaces as indent.
* The default compact formatting style:
* <ul>
* <li>no newline
* <li>no indent
* <li>no space after {@code ','} and {@code ':'}
* </ul>
*/
public static final FormattingStyle DEFAULT =
new FormattingStyle("\n", " ");
public static final FormattingStyle COMPACT = new FormattingStyle("", "", false);

private FormattingStyle(String newline, String indent) {
/**
* The default pretty printing formatting style:
* <ul>
* <li>{@code "\n"} as newline
* <li>two spaces as indent
* <li>a space between {@code ':'} and the subsequent value
* </ul>
*/
public static final FormattingStyle PRETTY =
new FormattingStyle("\n", " ", true);

private FormattingStyle(String newline, String indent, boolean spaceAfterSeparators) {
Objects.requireNonNull(newline, "newline == null");
Objects.requireNonNull(indent, "indent == null");
if (!newline.matches("[\r\n]*")) {
Expand All @@ -55,6 +75,7 @@ private FormattingStyle(String newline, String indent) {
}
this.newline = newline;
this.indent = indent;
this.spaceAfterSeparators = spaceAfterSeparators;
}

/**
Expand All @@ -70,7 +91,7 @@ private FormattingStyle(String newline, String indent) {
* @return a newly created {@link FormattingStyle}
*/
public FormattingStyle withNewline(String newline) {
return new FormattingStyle(newline, this.indent);
return new FormattingStyle(newline, this.indent, this.spaceAfterSeparators);
}

/**
Expand All @@ -82,11 +103,26 @@ public FormattingStyle withNewline(String newline) {
* @return a newly created {@link FormattingStyle}
*/
public FormattingStyle withIndent(String indent) {
return new FormattingStyle(this.newline, indent);
return new FormattingStyle(this.newline, indent, this.spaceAfterSeparators);
}

/**
* Creates a {@link FormattingStyle} which either uses a space after
* the separators {@code ','} and {@code ':'} in the JSON output, or not.
*
* <p>This setting has no effect on the {@linkplain #withNewline(String) configured newline}.
* If a non-empty newline is configured, it will always be added after
* {@code ','} and no space is added after the {@code ','} in that case.</p>
*
* @param spaceAfterSeparators whether to output a space after {@code ','} and {@code ':'}.
* @return a newly created {@link FormattingStyle}
*/
public FormattingStyle withSpaceAfterSeparators(boolean spaceAfterSeparators) {
return new FormattingStyle(this.newline, this.indent, spaceAfterSeparators);
}

/**
* The string value that will be used as a newline.
* Returns the string value that will be used as a newline.
*
* @return the newline value.
*/
Expand All @@ -95,11 +131,18 @@ public String getNewline() {
}

/**
* The string value that will be used as indent.
* Returns the string value that will be used as indent.
*
* @return the indent value.
*/
public String getIndent() {
return this.indent;
}

/**
* Returns whether a space will be used after {@code ','} and {@code ':'}.
*/
public boolean usesSpaceAfterSeparators() {
return this.spaceAfterSeparators;
}
}
8 changes: 4 additions & 4 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
* List&lt;MyType&gt; target2 = gson.fromJson(json, listType);
* </pre>
*
* <p>See the <a href="https://github.com/google/gson/blob/master/UserGuide.md">Gson User Guide</a>
* <p>See the <a href="https://github.com/google/gson/blob/main/UserGuide.md">Gson User Guide</a>
* for a more complete set of examples.</p>
*
* <h2 id="default-lenient">Lenient JSON handling</h2>
Expand Down Expand Up @@ -141,7 +141,7 @@
public final class Gson {
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
static final boolean DEFAULT_LENIENT = false;
static final FormattingStyle DEFAULT_FORMATTING_STYLE = null;
static final FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT;
static final boolean DEFAULT_ESCAPE_HTML = true;
static final boolean DEFAULT_SERIALIZE_NULLS = false;
static final boolean DEFAULT_COMPLEX_MAP_KEYS = false;
Expand Down Expand Up @@ -205,7 +205,7 @@ public final class Gson {
* means that all the unneeded white-space is removed. You can change this behavior with
* {@link GsonBuilder#setPrettyPrinting()}.</li>
* <li>When the JSON generated contains more than one line, the kind of newline and indent to
* use can be configured with {@link GsonBuilder#setPrettyPrinting(FormattingStyle)}.</li>
* use can be configured with {@link GsonBuilder#setFormattingStyle(FormattingStyle)}.</li>
* <li>The generated JSON omits all the fields that are null. Note that nulls in arrays are
* kept as is since an array is an ordered list. Moreover, if a field is not null, but its
* generated JSON is empty, the field is kept. You can configure Gson to serialize null values
Expand Down Expand Up @@ -894,7 +894,7 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce
* <li>{@link GsonBuilder#serializeNulls()}</li>
* <li>{@link GsonBuilder#setLenient()}</li>
* <li>{@link GsonBuilder#setPrettyPrinting()}</li>
* <li>{@link GsonBuilder#setPrettyPrinting(FormattingStyle)}</li>
* <li>{@link GsonBuilder#setFormattingStyle(FormattingStyle)}</li>
* </ul>
*/
public JsonWriter newJsonWriter(Writer writer) throws IOException {
Expand Down
12 changes: 5 additions & 7 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -497,29 +497,27 @@ public GsonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strateg
* Configures Gson to output JSON that fits in a page for pretty printing. This option only
* affects JSON serialization.
*
* <p>This is a convenience method which simply calls {@link #setPrettyPrinting(FormattingStyle)}
* with {@link FormattingStyle#DEFAULT}.
* <p>This is a convenience method which simply calls {@link #setFormattingStyle(FormattingStyle)}
* with {@link FormattingStyle#PRETTY}.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
@CanIgnoreReturnValue
public GsonBuilder setPrettyPrinting() {
return setPrettyPrinting(FormattingStyle.DEFAULT);
return setFormattingStyle(FormattingStyle.PRETTY);
}

/**
* Configures Gson to output JSON that uses a certain kind of formatting style (for example newline and indent).
* This option only affects JSON serialization. By default Gson produces compact JSON output without any formatting.
*
* <p>Has no effect if the serialized format is a single line.</p>
*
* @param formattingStyle the formatting style to use.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since $next-version$
*/
@CanIgnoreReturnValue
public GsonBuilder setPrettyPrinting(FormattingStyle formattingStyle) {
this.formattingStyle = formattingStyle;
public GsonBuilder setFormattingStyle(FormattingStyle formattingStyle) {
this.formattingStyle = Objects.requireNonNull(formattingStyle);
return this;
}

Expand Down
Loading

0 comments on commit fdc0d67

Please sign in to comment.