xref: /aosp_15_r20/external/gson/Troubleshooting.md (revision a8de600362638ea28fd6cb3225451dc706d269bb)
1*a8de6003SAndroid Build Coastguard Worker# Troubleshooting Guide
2*a8de6003SAndroid Build Coastguard Worker
3*a8de6003SAndroid Build Coastguard WorkerThis guide describes how to troubleshoot common issues when using Gson.
4*a8de6003SAndroid Build Coastguard Worker
5*a8de6003SAndroid Build Coastguard Worker## `ClassCastException` when using deserialized object
6*a8de6003SAndroid Build Coastguard Worker
7*a8de6003SAndroid Build Coastguard Worker**Symptom:** `ClassCastException` is thrown when accessing an object deserialized by Gson
8*a8de6003SAndroid Build Coastguard Worker
9*a8de6003SAndroid Build Coastguard Worker**Reason:** Your code is most likely not type-safe
10*a8de6003SAndroid Build Coastguard Worker
11*a8de6003SAndroid Build Coastguard Worker**Solution:** Make sure your code adheres to the following:
12*a8de6003SAndroid Build Coastguard Worker
13*a8de6003SAndroid Build Coastguard Worker- Avoid raw types: Instead of calling `fromJson(..., List.class)`, create for example a `TypeToken<List<MyClass>>`.
14*a8de6003SAndroid Build Coastguard Worker  See the [user guide](UserGuide.md#collections-examples) for more information.
15*a8de6003SAndroid Build Coastguard Worker- When using `TypeToken` prefer the `Gson.fromJson` overloads with `TypeToken` parameter such as [`fromJson(Reader, TypeToken)`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/Gson.html#fromJson(java.io.Reader,com.google.gson.reflect.TypeToken)).
16*a8de6003SAndroid Build Coastguard Worker  The overloads with `Type` parameter do not provide any type-safety guarantees.
17*a8de6003SAndroid Build Coastguard Worker- When using `TypeToken` make sure you don't capture a type variable. For example avoid something like `new TypeToken<List<T>>()` (where `T` is a type variable). Due to Java type erasure the actual type of `T` is not available at runtime. Refactor your code to pass around `TypeToken` instances or use [`TypeToken.getParameterized(...)`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/reflect/TypeToken.html#getParameterized(java.lang.reflect.Type,java.lang.reflect.Type...)), for example `TypeToken.getParameterized(List.class, elementClass)`.
18*a8de6003SAndroid Build Coastguard Worker
19*a8de6003SAndroid Build Coastguard Worker## `InaccessibleObjectException`: 'module ... does not "opens ..." to unnamed module'
20*a8de6003SAndroid Build Coastguard Worker
21*a8de6003SAndroid Build Coastguard Worker**Symptom:** An exception with a message in the form 'module ... does not "opens ..." to unnamed module' is thrown
22*a8de6003SAndroid Build Coastguard Worker
23*a8de6003SAndroid Build Coastguard Worker**Reason:** You use Gson by accident to access internal fields of third-party classes
24*a8de6003SAndroid Build Coastguard Worker
25*a8de6003SAndroid Build Coastguard Worker**Solution:** Write custom Gson [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) implementations for the affected classes or change the type of your data
26*a8de6003SAndroid Build Coastguard Worker
27*a8de6003SAndroid Build Coastguard Worker**Explanation:**
28*a8de6003SAndroid Build Coastguard Worker
29*a8de6003SAndroid Build Coastguard WorkerWhen no built-in adapter for a type exists and no custom adapter has been registered, Gson falls back to using reflection to access the fields of a class (including `private` ones). Most likely you are seeing this error because you (by accident) rely on the reflection-based adapter for third-party classes. That should be avoided because you make yourself dependent on the implementation details of these classes which could change at any point. For the JDK it is also not possible anymore to access internal fields using reflection starting with JDK 17, see [JEP 403](https://openjdk.org/jeps/403).
30*a8de6003SAndroid Build Coastguard Worker
31*a8de6003SAndroid Build Coastguard WorkerIf you want to prevent using reflection on third-party classes in the future you can write your own [`ReflectionAccessFilter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/ReflectionAccessFilter.html) or use one of the predefined ones, such as `ReflectionAccessFilter.BLOCK_ALL_PLATFORM`.
32*a8de6003SAndroid Build Coastguard Worker
33*a8de6003SAndroid Build Coastguard Worker## `InaccessibleObjectException`: 'module ... does not "opens ..." to module com.google.gson'
34*a8de6003SAndroid Build Coastguard Worker
35*a8de6003SAndroid Build Coastguard Worker**Symptom:** An exception with a message in the form 'module ... does not "opens ..." to module com.google.gson' is thrown
36*a8de6003SAndroid Build Coastguard Worker
37*a8de6003SAndroid Build Coastguard Worker**Reason:**
38*a8de6003SAndroid Build Coastguard Worker
39*a8de6003SAndroid Build Coastguard Worker- If the reported package is your own package then you have not configured the module declaration of your project to allow Gson to use reflection on your classes.
40*a8de6003SAndroid Build Coastguard Worker- If the reported package is from a third party library or the JDK see [this troubleshooting point](#inaccessibleobjectexception-module--does-not-opens--to-unnamed-module).
41*a8de6003SAndroid Build Coastguard Worker
42*a8de6003SAndroid Build Coastguard Worker**Solution:** Make sure the `module-info.java` file of your project allows Gson to use reflection on your classes, for example:
43*a8de6003SAndroid Build Coastguard Worker
44*a8de6003SAndroid Build Coastguard Worker```java
45*a8de6003SAndroid Build Coastguard Workermodule mymodule {
46*a8de6003SAndroid Build Coastguard Worker    requires com.google.gson;
47*a8de6003SAndroid Build Coastguard Worker
48*a8de6003SAndroid Build Coastguard Worker    opens mypackage to com.google.gson;
49*a8de6003SAndroid Build Coastguard Worker}
50*a8de6003SAndroid Build Coastguard Worker```
51*a8de6003SAndroid Build Coastguard Worker
52*a8de6003SAndroid Build Coastguard Worker## Android app not working in Release mode; random property names
53*a8de6003SAndroid Build Coastguard Worker
54*a8de6003SAndroid Build Coastguard Worker**Symptom:** Your Android app is working fine in Debug mode but fails in Release mode and the JSON properties have seemingly random names such as `a`, `b`, ...
55*a8de6003SAndroid Build Coastguard Worker
56*a8de6003SAndroid Build Coastguard Worker**Reason:** You probably have not configured ProGuard / R8 correctly
57*a8de6003SAndroid Build Coastguard Worker
58*a8de6003SAndroid Build Coastguard Worker**Solution:** Make sure you have configured ProGuard / R8 correctly to preserve the names of your fields. See the [Android example](examples/android-proguard-example/README.md) for more information.
59*a8de6003SAndroid Build Coastguard Worker
60*a8de6003SAndroid Build Coastguard Worker## Android app unable to parse JSON after app update
61*a8de6003SAndroid Build Coastguard Worker
62*a8de6003SAndroid Build Coastguard Worker**Symptom:** You released a new version of your Android app and it fails to parse JSON data created by the previous version of your app
63*a8de6003SAndroid Build Coastguard Worker
64*a8de6003SAndroid Build Coastguard Worker**Reason:** You probably have not configured ProGuard / R8 correctly; probably the fields names are being obfuscated and their naming changed between the versions of your app
65*a8de6003SAndroid Build Coastguard Worker
66*a8de6003SAndroid Build Coastguard Worker**Solution:** Make sure you have configured ProGuard / R8 correctly to preserve the names of your fields. See the [Android example](examples/android-proguard-example/README.md) for more information.
67*a8de6003SAndroid Build Coastguard Worker
68*a8de6003SAndroid Build Coastguard WorkerIf you want to preserve backward compatibility for you app you can use [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) on the fields to specify the obfuscated name as alternate, for example: `@SerializedName(value = "myprop", alternate = "a")`
69*a8de6003SAndroid Build Coastguard Worker
70*a8de6003SAndroid Build Coastguard WorkerNormally ProGuard and R8 produce a mapping file, this makes it easier to find out the obfuscated field names instead of having to find them out through trial and error or other means. See the [Android Studio user guide](https://developer.android.com/studio/build/shrink-code.html#retracing) for more information.
71*a8de6003SAndroid Build Coastguard Worker
72*a8de6003SAndroid Build Coastguard Worker## Default field values not present after deserialization
73*a8de6003SAndroid Build Coastguard Worker
74*a8de6003SAndroid Build Coastguard Worker**Symptom:** You have assign default values to fields but after deserialization the fields have their standard value (such as `null` or `0`)
75*a8de6003SAndroid Build Coastguard Worker
76*a8de6003SAndroid Build Coastguard Worker**Reason:** Gson cannot invoke the constructor of your class and falls back to JDK `Unsafe` (or similar means)
77*a8de6003SAndroid Build Coastguard Worker
78*a8de6003SAndroid Build Coastguard Worker**Solution:** Make sure that the class:
79*a8de6003SAndroid Build Coastguard Worker
80*a8de6003SAndroid Build Coastguard Worker- is `static` (explicitly or implicitly when it is a top-level class)
81*a8de6003SAndroid Build Coastguard Worker- has a no-args constructor
82*a8de6003SAndroid Build Coastguard Worker
83*a8de6003SAndroid Build Coastguard WorkerOtherwise Gson will by default try to use JDK `Unsafe` or similar means to create an instance of your class without invoking the constructor and without running any initializers. You can also disable that behavior through [`GsonBuilder.disableJdkUnsafe()`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#disableJdkUnsafe()) to notice such issues early on.
84*a8de6003SAndroid Build Coastguard Worker
85*a8de6003SAndroid Build Coastguard Worker## `null` values for anonymous and local classes
86*a8de6003SAndroid Build Coastguard Worker
87*a8de6003SAndroid Build Coastguard Worker**Symptom:** Objects of a class are always serialized as JSON `null` / always deserialized as Java `null`
88*a8de6003SAndroid Build Coastguard Worker
89*a8de6003SAndroid Build Coastguard Worker**Reason:** The class you are serializing or deserializing is an anonymous or a local class (or you have specified a custom `ExclusionStrategy`)
90*a8de6003SAndroid Build Coastguard Worker
91*a8de6003SAndroid Build Coastguard Worker**Solution:** Convert the class to a `static` nested class. If the class is already `static` make sure you have not specified a Gson `ExclusionStrategy` which might exclude the class.
92*a8de6003SAndroid Build Coastguard Worker
93*a8de6003SAndroid Build Coastguard WorkerNotes:
94*a8de6003SAndroid Build Coastguard Worker
95*a8de6003SAndroid Build Coastguard Worker- "double brace-initialization" also creates anonymous classes
96*a8de6003SAndroid Build Coastguard Worker- Local record classes (feature added in Java 16) are supported by Gson and are not affected by this
97*a8de6003SAndroid Build Coastguard Worker
98*a8de6003SAndroid Build Coastguard Worker## Map keys having unexpected format in JSON
99*a8de6003SAndroid Build Coastguard Worker
100*a8de6003SAndroid Build Coastguard Worker**Symptom:** JSON output for `Map` keys is unexpected / cannot be deserialized again
101*a8de6003SAndroid Build Coastguard Worker
102*a8de6003SAndroid Build Coastguard Worker**Reason:** The `Map` key type is 'complex' and you have not configured the `GsonBuilder` properly
103*a8de6003SAndroid Build Coastguard Worker
104*a8de6003SAndroid Build Coastguard Worker**Solution:** Use [`GsonBuilder.enableComplexMapKeySerialization()`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#enableComplexMapKeySerialization()). See also the [user guide](UserGuide.md#maps-examples) for more information.
105*a8de6003SAndroid Build Coastguard Worker
106*a8de6003SAndroid Build Coastguard Worker## Parsing JSON fails with `MalformedJsonException`
107*a8de6003SAndroid Build Coastguard Worker
108*a8de6003SAndroid Build Coastguard Worker**Symptom:** JSON parsing fails with `MalformedJsonException`
109*a8de6003SAndroid Build Coastguard Worker
110*a8de6003SAndroid Build Coastguard Worker**Reason:** The JSON data is actually malformed
111*a8de6003SAndroid Build Coastguard Worker
112*a8de6003SAndroid Build Coastguard Worker**Solution:** During debugging log the JSON data right before calling Gson methods or set a breakpoint to inspect the data and make sure it has the expected format. Sometimes APIs might return HTML error pages (instead of JSON data) when reaching rate limits or when other errors occur. Also read the location information of the `MalformedJsonException` exception message, it indicates where exactly in the document the malformed data was detected, including the [JSONPath](https://goessner.net/articles/JsonPath/).
113*a8de6003SAndroid Build Coastguard Worker
114*a8de6003SAndroid Build Coastguard Worker## Integral JSON number is parsed as `double`
115*a8de6003SAndroid Build Coastguard Worker
116*a8de6003SAndroid Build Coastguard Worker**Symptom:** JSON data contains an integral number such as `45` but Gson returns it as `double`
117*a8de6003SAndroid Build Coastguard Worker
118*a8de6003SAndroid Build Coastguard Worker**Reason:** When parsing a JSON number as `Object`, Gson will by default create always return a `double`
119*a8de6003SAndroid Build Coastguard Worker
120*a8de6003SAndroid Build Coastguard Worker**Solution:** Use [`GsonBuilder.setObjectToNumberStrategy`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#setObjectToNumberStrategy(com.google.gson.ToNumberStrategy)) to specify what type of number should be returned
121*a8de6003SAndroid Build Coastguard Worker
122*a8de6003SAndroid Build Coastguard Worker## Malformed JSON not rejected
123*a8de6003SAndroid Build Coastguard Worker
124*a8de6003SAndroid Build Coastguard Worker**Symptom:** Gson parses malformed JSON without throwing any exceptions
125*a8de6003SAndroid Build Coastguard Worker
126*a8de6003SAndroid Build Coastguard Worker**Reason:** Due to legacy reasons Gson performs parsing by default in lenient mode
127*a8de6003SAndroid Build Coastguard Worker
128*a8de6003SAndroid Build Coastguard Worker**Solution:** See [`Gson` class documentation](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/Gson.html) section "Lenient JSON handling"
129*a8de6003SAndroid Build Coastguard Worker
130*a8de6003SAndroid Build Coastguard WorkerNote: Even in non-lenient mode Gson deviates slightly from the JSON specification, see [`JsonReader.setLenient`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonReader.html#setLenient(boolean)) for more details.
131*a8de6003SAndroid Build Coastguard Worker
132*a8de6003SAndroid Build Coastguard Worker## `IllegalStateException`: "Expected ... but was ..."
133*a8de6003SAndroid Build Coastguard Worker
134*a8de6003SAndroid Build Coastguard Worker**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was ..." is thrown
135*a8de6003SAndroid Build Coastguard Worker
136*a8de6003SAndroid Build Coastguard Worker**Reason:** The JSON data does not have the correct format
137*a8de6003SAndroid Build Coastguard Worker
138*a8de6003SAndroid Build Coastguard Worker**Solution:** Make sure that your classes correctly model the JSON data. Also during debugging log the JSON data right before calling Gson methods or set a breakpoint to inspect the data and make sure it has the expected format. Read the location information of the exception message, it indicates where exactly in the document the error occurred, including the [JSONPath](https://goessner.net/articles/JsonPath/).
139*a8de6003SAndroid Build Coastguard Worker
140*a8de6003SAndroid Build Coastguard Worker## `IllegalStateException`: "Expected ... but was NULL"
141*a8de6003SAndroid Build Coastguard Worker
142*a8de6003SAndroid Build Coastguard Worker**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was NULL" is thrown
143*a8de6003SAndroid Build Coastguard Worker
144*a8de6003SAndroid Build Coastguard Worker**Reason:** You have written a custom `TypeAdapter` which does not properly handle a JSON null value
145*a8de6003SAndroid Build Coastguard Worker
146*a8de6003SAndroid Build Coastguard Worker**Solution:** Add code similar to the following at the beginning of the `read` method of your adapter:
147*a8de6003SAndroid Build Coastguard Worker
148*a8de6003SAndroid Build Coastguard Worker```java
149*a8de6003SAndroid Build Coastguard Worker@Override
150*a8de6003SAndroid Build Coastguard Workerpublic MyClass read(JsonReader in) throws IOException {
151*a8de6003SAndroid Build Coastguard Worker    if (in.peek() == JsonToken.NULL) {
152*a8de6003SAndroid Build Coastguard Worker        in.nextNull();
153*a8de6003SAndroid Build Coastguard Worker        return null;
154*a8de6003SAndroid Build Coastguard Worker    }
155*a8de6003SAndroid Build Coastguard Worker
156*a8de6003SAndroid Build Coastguard Worker    ...
157*a8de6003SAndroid Build Coastguard Worker}
158*a8de6003SAndroid Build Coastguard Worker```
159*a8de6003SAndroid Build Coastguard Worker
160*a8de6003SAndroid Build Coastguard WorkerAlternatively you can call [`nullSafe()`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html#nullSafe()) on the adapter instance you created.
161*a8de6003SAndroid Build Coastguard Worker
162*a8de6003SAndroid Build Coastguard Worker## Properties missing in JSON
163*a8de6003SAndroid Build Coastguard Worker
164*a8de6003SAndroid Build Coastguard Worker**Symptom:** Properties are missing in the JSON output
165*a8de6003SAndroid Build Coastguard Worker
166*a8de6003SAndroid Build Coastguard Worker**Reason:** Gson by default omits JSON null from the output (or: ProGuard / R8 is not configured correctly and removed unused fields)
167*a8de6003SAndroid Build Coastguard Worker
168*a8de6003SAndroid Build Coastguard Worker**Solution:** Use [`GsonBuilder.serializeNulls()`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#serializeNulls())
169*a8de6003SAndroid Build Coastguard Worker
170*a8de6003SAndroid Build Coastguard WorkerNote: Gson does not support anonymous and local classes and will serialize them as JSON null, see the [related troubleshooting point](#null-values-for-anonymous-and-local-classes).
171*a8de6003SAndroid Build Coastguard Worker
172*a8de6003SAndroid Build Coastguard Worker## JSON output changes for newer Android versions
173*a8de6003SAndroid Build Coastguard Worker
174*a8de6003SAndroid Build Coastguard Worker**Symptom:** The JSON output differs when running on newer Android versions
175*a8de6003SAndroid Build Coastguard Worker
176*a8de6003SAndroid Build Coastguard Worker**Reason:** You use Gson by accident to access internal fields of Android classes
177*a8de6003SAndroid Build Coastguard Worker
178*a8de6003SAndroid Build Coastguard Worker**Solution:** Write custom Gson [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) implementations for the affected classes or change the type of your data
179*a8de6003SAndroid Build Coastguard Worker
180*a8de6003SAndroid Build Coastguard Worker**Explanation:**
181*a8de6003SAndroid Build Coastguard Worker
182*a8de6003SAndroid Build Coastguard WorkerWhen no built-in adapter for a type exists and no custom adapter has been registered, Gson falls back to using reflection to access the fields of a class (including `private` ones). Most likely you are experiencing this issue because you (by accident) rely on the reflection-based adapter for Android classes. That should be avoided because you make yourself dependent on the implementation details of these classes which could change at any point.
183*a8de6003SAndroid Build Coastguard Worker
184*a8de6003SAndroid Build Coastguard WorkerIf you want to prevent using reflection on third-party classes in the future you can write your own [`ReflectionAccessFilter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/ReflectionAccessFilter.html) or use one of the predefined ones, such as `ReflectionAccessFilter.BLOCK_ALL_PLATFORM`.
185*a8de6003SAndroid Build Coastguard Worker
186*a8de6003SAndroid Build Coastguard Worker## JSON output contains values of `static` fields
187*a8de6003SAndroid Build Coastguard Worker
188*a8de6003SAndroid Build Coastguard Worker**Symptom:** The JSON output contains values of `static` fields
189*a8de6003SAndroid Build Coastguard Worker
190*a8de6003SAndroid Build Coastguard Worker**Reason:** You used `GsonBuilder.excludeFieldsWithModifiers` to overwrite the default excluded modifiers
191*a8de6003SAndroid Build Coastguard Worker
192*a8de6003SAndroid Build Coastguard Worker**Solution:** When calling `GsonBuilder.excludeFieldsWithModifiers` you overwrite the default excluded modifiers. Therefore, you have to explicitly exclude `static` fields if desired. This can be done by adding `| Modifier.STATIC` to the argument.
193