diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d8ec995e0..50f6b3d0d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,9 +19,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: "Set up JDK ${{ matrix.java }}" - uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: distribution: 'temurin' java-version: ${{ matrix.java }} @@ -35,9 +35,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: "Set up GraalVM" - uses: graalvm/setup-graalvm@5393c3d80982e8a7fa61005137824ef53731ff9a # v1.1.8.1 + uses: graalvm/setup-graalvm@2f25c0caae5b220866f732832d5e3e29ff493338 # v1.2.1 with: java-version: '17' distribution: 'graalvm' @@ -54,9 +54,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: "Set up JDK 17" - uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: distribution: 'temurin' java-version: 17 diff --git a/.github/workflows/check-android-compatibility.yml b/.github/workflows/check-android-compatibility.yml index ac1499b915..9122d4207c 100644 --- a/.github/workflows/check-android-compatibility.yml +++ b/.github/workflows/check-android-compatibility.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set up JDK 11 - uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: distribution: 'temurin' java-version: '11' diff --git a/.github/workflows/check-api-compatibility.yml b/.github/workflows/check-api-compatibility.yml index 75f9dee7ce..635ed4ef63 100644 --- a/.github/workflows/check-api-compatibility.yml +++ b/.github/workflows/check-api-compatibility.yml @@ -10,13 +10,13 @@ jobs: steps: - name: Checkout old version - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ github.event.pull_request.base.sha }} path: 'gson-old-japicmp' - name: Set up JDK 11 - uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: distribution: 'temurin' java-version: '11' @@ -31,7 +31,7 @@ jobs: mvn --batch-mode --no-transfer-progress install -DskipTests - name: Checkout new version - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Check API compatibility id: check-compatibility @@ -39,7 +39,7 @@ jobs: mvn --batch-mode --fail-at-end --no-transfer-progress package japicmp:cmp -DskipTests - name: Upload API differences artifacts - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 # Run on workflow success (in that case differences report might include added methods and classes) # or when API compatibility check failed if: success() || ( failure() && steps.check-compatibility.outcome == 'failure' ) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 0595f8aa9e..90b709dac2 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -18,7 +18,7 @@ jobs: fuzz-seconds: 600 dry-run: false - name: Upload Crash - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4c9e14e3b0..daa61677a0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,10 +25,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set up JDK 17 - uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 with: distribution: 'temurin' java-version: '17' @@ -36,7 +36,7 @@ jobs: # Initializes the CodeQL tools for scanning - name: Initialize CodeQL - uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: languages: ${{ matrix.language }} # Run all security queries and maintainability and reliability queries @@ -50,4 +50,4 @@ jobs: mvn compile --batch-mode --no-transfer-progress - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 + uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 diff --git a/README.md b/README.md index cd12727192..a4a674ba64 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ There are a few open-source projects that can convert Java objects to JSON. Howe > [!NOTE]\ > Gson is currently in maintenance mode; existing bugs will be fixed, but large new features will likely not be added. If you want to add a new feature, please first search for existing GitHub issues, or create a new one to discuss the feature and get feedback. +> [!IMPORTANT]\ +> Gson's main focus is on Java. Using it with other JVM languages such as Kotlin or Scala might work fine in many cases, but language-specific features such as Kotlin's non-`null` types or constructors with default arguments are not supported. This can lead to confusing and incorrect behavior.\ +> When using languages other than Java, prefer a JSON library with explicit support for that language. + ### Goals * Provide simple `toJson()` and `fromJson()` methods to convert Java objects to JSON and vice-versa * Allow pre-existing unmodifiable objects to be converted to and from JSON diff --git a/Troubleshooting.md b/Troubleshooting.md index 17c0595962..0c7ea794c6 100644 --- a/Troubleshooting.md +++ b/Troubleshooting.md @@ -9,7 +9,10 @@ This guide describes how to troubleshoot common issues when using Gson. **Symptom:** `ClassCastException` is thrown when accessing an object deserialized by Gson -**Reason:** Your code is most likely not type-safe +**Reason:** + +- Your code is most likely not type-safe +- Or, you have not configured code shrinking tools such as ProGuard or R8 correctly **Solution:** Make sure your code adheres to the following: @@ -19,6 +22,8 @@ This guide describes how to troubleshoot common issues when using Gson. The overloads with `Type` parameter do not provide any type-safety guarantees. - When using `TypeToken` make sure you don't capture a type variable. For example avoid something like `new TypeToken>()` (where `T` is a type variable). Due to Java [type erasure](https://dev.java/learn/generics/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, elementType)` where `elementType` is a type you have to provide separately. +If you are using a code shrinking tool such as ProGuard or R8 (for example when building an Android app), make sure it is correctly configured to keep generic signatures and to keep Gson's `TypeToken` class. See the [Android example](examples/android-proguard-example/README.md) for more information. + ## `InaccessibleObjectException`: 'module ... does not "opens ..." to unnamed module' **Symptom:** An exception with a message in the form 'module ... does not "opens ..." to unnamed module' is thrown @@ -197,7 +202,7 @@ Gson then tries to use reflection and expects that the data is a JSON object (he **Reason:** - A built-in adapter does not support JSON null values -- You have written a custom `TypeAdapter` which does not properly handle JSON null values +- Or, you have written a custom `TypeAdapter` which does not properly handle JSON null values **Solution:** If this occurs for a custom adapter you wrote, add code similar to the following at the beginning of its `read` method: @@ -245,7 +250,7 @@ If you want to prevent using reflection on third-party classes in the future you **Reason:** You used `GsonBuilder.excludeFieldsWithModifiers` to overwrite the default excluded modifiers -**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` as additional argument. +**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` as additional argument to the `excludeFieldsWithModifiers` call. ## `NoSuchMethodError` when calling Gson methods @@ -272,12 +277,13 @@ If that fails with a `NullPointerException` you have to try one of the other way **Reason:** - The name you have specified with a [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) annotation for a field collides with the name of another field -- The [`FieldNamingStrategy`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/FieldNamingStrategy.html) you have specified produces conflicting field names -- A field of your class has the same name as the field of a superclass +- Or, the [`FieldNamingStrategy`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/FieldNamingStrategy.html) you have specified produces conflicting field names +- Or, a field of your class has the same name as the field of a superclass +- Or, you are using an obfuscation tool such as ProGuard or R8 and it has renamed the fields; in that case see [this troubleshooting point](#android-app-random-names) Gson prevents multiple fields with the same name because during deserialization it would be ambiguous for which field the JSON data should be deserialized. For serialization it would cause the same field to appear multiple times in JSON. While the JSON specification permits this, it is likely that the application parsing the JSON data will not handle it correctly. -**Solution:** First identify the fields with conflicting names based on the exception message. Then decide if you want to rename one of them using the [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) annotation, or if you want to [exclude](UserGuide.md#excluding-fields-from-serialization-and-deserialization) one of them. When excluding one of the fields you have to include it for both serialization and deserialization (even if your application only performs one of these actions) because the duplicate field check cannot differentiate between these actions. +**Solution:** First identify the fields with conflicting names based on the exception message. Then decide if you want to rename one of them using the [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) annotation, or if you want to [exclude](UserGuide.md#excluding-fields-from-serialization-and-deserialization) one of them. When excluding one of the fields you have to apply the exclusion for both serialization and deserialization (even if your application only performs one of these actions) because the duplicate field check cannot differentiate between these actions. ## `UnsupportedOperationException` when serializing or deserializing `java.lang.Class` @@ -325,7 +331,7 @@ For older Gson versions a `RuntimeException` with message 'Missing type paramete **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>() {}`. 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. +- Or, 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: diff --git a/gson/pom.xml b/gson/pom.xml index 46876d31b6..ea3656fa85 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -50,7 +50,7 @@ com.google.errorprone error_prone_annotations - 2.25.0 + 2.27.0 @@ -66,7 +66,7 @@ com.google.guava guava-testlib - 33.0.0-jre + 33.1.0-jre test @@ -201,7 +201,7 @@ com.guardsquare proguard-core - 9.1.1 + 9.1.3 @@ -263,7 +263,7 @@ org.moditect moditect-maven-plugin - 1.1.0 + 1.2.1.Final add-module-info diff --git a/gson/src/main/java/com/google/gson/JsonArray.java b/gson/src/main/java/com/google/gson/JsonArray.java index 0d1cbd2f17..10b94c0dc1 100644 --- a/gson/src/main/java/com/google/gson/JsonArray.java +++ b/gson/src/main/java/com/google/gson/JsonArray.java @@ -33,6 +33,9 @@ *

{@code JsonArray} only implements the {@link Iterable} interface but not the {@link List} * interface. A {@code List} view of it can be obtained with {@link #asList()}. * + *

See the {@link JsonElement} documentation for details on how to convert {@code JsonArray} and + * generally any {@code JsonElement} from and to JSON. + * * @author Inderjeet Singh * @author Joel Leitch */ diff --git a/gson/src/main/java/com/google/gson/JsonElement.java b/gson/src/main/java/com/google/gson/JsonElement.java index e75d2686de..b7d135b978 100644 --- a/gson/src/main/java/com/google/gson/JsonElement.java +++ b/gson/src/main/java/com/google/gson/JsonElement.java @@ -29,7 +29,7 @@ * A class representing an element of JSON. It could either be a {@link JsonObject}, a {@link * JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}. * - *

This class provides multiple {@code getAs...} methods which allow + *

This class provides multiple {@code getAs} methods which allow * *

    *
  • obtaining the represented primitive value, for example {@link #getAsString()} @@ -53,11 +53,11 @@ * *
* - * To convert a {@code JsonElement} to JSON the method {@link Gson#toJson(JsonElement)} and its - * overloads can be used. + * To convert a {@code JsonElement} to JSON either {@link #toString() JsonElement.toString()} or the + * method {@link Gson#toJson(JsonElement)} and its overloads can be used. * *

It is also possible to obtain the {@link TypeAdapter} for {@code JsonElement} from a {@link - * Gson} instance and then use it for conversion to JSON: + * Gson} instance and then use it for conversion from and to JSON: * *

{@code
  * TypeAdapter adapter = gson.getAdapter(JsonElement.class);
@@ -384,7 +384,41 @@ public short getAsShort() {
     throw new UnsupportedOperationException(getClass().getSimpleName());
   }
 
-  /** Returns a String representation of this element. */
+  /**
+   * Converts this element to a JSON string.
+   *
+   * 

For example: + * + *

+   * JsonObject object = new JsonObject();
+   * object.add("a", JsonNull.INSTANCE);
+   * JsonArray array = new JsonArray();
+   * array.add(1);
+   * object.add("b", array);
+   *
+   * String json = object.toString();
+   * // json: {"a":null,"b":[1]}
+   * 
+ * + * If this element or any nested elements contain {@link Double#NaN NaN} or {@link + * Double#isInfinite() Infinity} that value is written to JSON, even though the JSON specification + * does not permit these values. + * + *

To customize formatting or to directly write to an {@link Appendable} instead of creating an + * intermediate {@code String} first, use {@link Gson#toJson(JsonElement, Appendable) + * Gson.toJson(JsonElement, ...)}. + * + *

To get the contained String value (without enclosing {@code "} and without escaping), use + * {@link #getAsString()} instead: + * + *

+   * JsonPrimitive jsonPrimitive = new JsonPrimitive("with \" quote");
+   * String json = jsonPrimitive.toString();
+   * // json: "with \" quote"
+   * String value = jsonPrimitive.getAsString();
+   * // value: with " quote
+   * 
+ */ @Override public String toString() { try { diff --git a/gson/src/main/java/com/google/gson/JsonObject.java b/gson/src/main/java/com/google/gson/JsonObject.java index d13be19dce..408d22685d 100644 --- a/gson/src/main/java/com/google/gson/JsonObject.java +++ b/gson/src/main/java/com/google/gson/JsonObject.java @@ -22,7 +22,7 @@ import java.util.Set; /** - * A class representing an object type in Json. An object consists of name-value pairs where names + * A class representing an object type in JSON. An object consists of name-value pairs where names * are strings, and values are any other type of {@link JsonElement}. This allows for a creating a * tree of JsonElements. The member elements of this object are maintained in order they were added. * This class does not support {@code null} values. If {@code null} is provided as value argument to @@ -31,6 +31,9 @@ *

{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view of it * can be obtained with {@link #asMap()}. * + *

See the {@link JsonElement} documentation for details on how to convert {@code JsonObject} and + * generally any {@code JsonElement} from and to JSON. + * * @author Inderjeet Singh * @author Joel Leitch */ diff --git a/gson/src/main/java/com/google/gson/JsonPrimitive.java b/gson/src/main/java/com/google/gson/JsonPrimitive.java index 2bce4be498..88f4466fc8 100644 --- a/gson/src/main/java/com/google/gson/JsonPrimitive.java +++ b/gson/src/main/java/com/google/gson/JsonPrimitive.java @@ -26,6 +26,9 @@ * A class representing a JSON primitive value. A primitive value is either a String, a Java * primitive, or a Java primitive wrapper type. * + *

See the {@link JsonElement} documentation for details on how to convert {@code JsonPrimitive} + * and generally any {@code JsonElement} from and to JSON. + * * @author Inderjeet Singh * @author Joel Leitch */ diff --git a/gson/src/main/java/com/google/gson/ToNumberPolicy.java b/gson/src/main/java/com/google/gson/ToNumberPolicy.java index 6380e5d986..7e9f85d0ca 100644 --- a/gson/src/main/java/com/google/gson/ToNumberPolicy.java +++ b/gson/src/main/java/com/google/gson/ToNumberPolicy.java @@ -68,22 +68,30 @@ public Number readNumber(JsonReader in) throws IOException { @Override public Number readNumber(JsonReader in) throws IOException, JsonParseException { String value = in.nextString(); - try { - return Long.parseLong(value); - } catch (NumberFormatException longE) { + if (value.indexOf('.') >= 0) { + return parseAsDouble(value, in); + } else { try { - Double d = Double.valueOf(value); - if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { - throw new MalformedJsonException( - "JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath()); - } - return d; - } catch (NumberFormatException doubleE) { - throw new JsonParseException( - "Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE); + return Long.parseLong(value); + } catch (NumberFormatException e) { + return parseAsDouble(value, in); } } } + + private Number parseAsDouble(String value, JsonReader in) throws IOException { + try { + Double d = Double.valueOf(value); + if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { + throw new MalformedJsonException( + "JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath()); + } + return d; + } catch (NumberFormatException e) { + throw new JsonParseException( + "Cannot parse " + value + "; at path " + in.getPreviousPath(), e); + } + } }, /** diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java index 58be43afc1..83186a1a42 100644 --- a/gson/src/main/java/com/google/gson/stream/JsonReader.java +++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java @@ -414,6 +414,8 @@ public final Strictness getStrictness() { /** * Consumes the next token from the JSON stream and asserts that it is the beginning of a new * array. + * + * @throws IllegalStateException if the next token is not the beginning of an array. */ public void beginArray() throws IOException { int p = peeked; @@ -432,6 +434,8 @@ public void beginArray() throws IOException { /** * Consumes the next token from the JSON stream and asserts that it is the end of the current * array. + * + * @throws IllegalStateException if the next token is not the end of an array. */ public void endArray() throws IOException { int p = peeked; @@ -450,6 +454,8 @@ public void endArray() throws IOException { /** * Consumes the next token from the JSON stream and asserts that it is the beginning of a new * object. + * + * @throws IllegalStateException if the next token is not the beginning of an object. */ public void beginObject() throws IOException { int p = peeked; @@ -467,6 +473,8 @@ public void beginObject() throws IOException { /** * Consumes the next token from the JSON stream and asserts that it is the end of the current * object. + * + * @throws IllegalStateException if the next token is not the end of an object. */ public void endObject() throws IOException { int p = peeked; @@ -857,7 +865,7 @@ private boolean isLiteral(char c) throws IOException { /** * Returns the next token, a {@link JsonToken#NAME property name}, and consumes it. * - * @throws IOException if the next token in the stream is not a property name. + * @throws IllegalStateException if the next token is not a property name. */ public String nextName() throws IOException { int p = peeked; @@ -883,7 +891,7 @@ public String nextName() throws IOException { * Returns the {@link JsonToken#STRING string} value of the next token, consuming it. If the next * token is a number, this method will return its string form. * - * @throws IllegalStateException if the next token is not a string or if this reader is closed. + * @throws IllegalStateException if the next token is not a string. */ public String nextString() throws IOException { int p = peeked; @@ -916,7 +924,7 @@ public String nextString() throws IOException { /** * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token, consuming it. * - * @throws IllegalStateException if the next token is not a boolean or if this reader is closed. + * @throws IllegalStateException if the next token is not a boolean. */ public boolean nextBoolean() throws IOException { int p = peeked; @@ -938,7 +946,7 @@ public boolean nextBoolean() throws IOException { /** * Consumes the next token from the JSON stream and asserts that it is a literal null. * - * @throws IllegalStateException if the next token is not null or if this reader is closed. + * @throws IllegalStateException if the next token is not a JSON null. */ public void nextNull() throws IOException { int p = peeked; @@ -958,7 +966,7 @@ public void nextNull() throws IOException { * token is a string, this method will attempt to parse it as a double using {@link * Double#parseDouble(String)}. * - * @throws IllegalStateException if the next token is not a literal value. + * @throws IllegalStateException if the next token is neither a number nor a string. * @throws NumberFormatException if the next literal value cannot be parsed as a double. * @throws MalformedJsonException if the next literal value is NaN or Infinity and this reader is * not {@link #setStrictness(Strictness) lenient}. @@ -1002,7 +1010,7 @@ public double nextDouble() throws IOException { * token is a string, this method will attempt to parse it as a long. If the next token's numeric * value cannot be exactly represented by a Java {@code long}, this method throws. * - * @throws IllegalStateException if the next token is not a literal value. + * @throws IllegalStateException if the next token is neither a number nor a string. * @throws NumberFormatException if the next literal value cannot be parsed as a number, or * exactly represented as a long. */ @@ -1169,7 +1177,7 @@ private String nextUnquotedValue() throws IOException { } String result = - (null == builder) ? new String(buffer, pos, i) : builder.append(buffer, pos, i).toString(); + (builder == null) ? new String(buffer, pos, i) : builder.append(buffer, pos, i).toString(); pos += i; return result; } @@ -1239,7 +1247,7 @@ private void skipUnquotedValue() throws IOException { * token is a string, this method will attempt to parse it as an int. If the next token's numeric * value cannot be exactly represented by a Java {@code int}, this method throws. * - * @throws IllegalStateException if the next token is not a literal value. + * @throws IllegalStateException if the next token is neither a number nor a string. * @throws NumberFormatException if the next literal value cannot be parsed as a number, or * exactly represented as an int. */ @@ -1293,7 +1301,12 @@ public int nextInt() throws IOException { return result; } - /** Closes this JSON reader and the underlying {@link Reader}. */ + /** + * Closes this JSON reader and the underlying {@link Reader}. + * + *

Using the JSON reader after it has been closed will throw an {@link IllegalStateException} + * in most cases. + */ @Override public void close() throws IOException { peeked = PEEKED_NONE; diff --git a/gson/src/test/java/com/google/gson/JsonArrayTest.java b/gson/src/test/java/com/google/gson/JsonArrayTest.java index 30942513fa..a140814464 100644 --- a/gson/src/test/java/com/google/gson/JsonArrayTest.java +++ b/gson/src/test/java/com/google/gson/JsonArrayTest.java @@ -376,4 +376,21 @@ public void testSameAddition() { assertThat(jsonArray.toString()) .isEqualTo("[\"a\",\"a\",true,true,1212,1212,34.34,34.34,null,null]"); } + + @Test + public void testToString() { + JsonArray array = new JsonArray(); + assertThat(array.toString()).isEqualTo("[]"); + + array.add(JsonNull.INSTANCE); + array.add(Float.NaN); + array.add("a\0"); + JsonArray nestedArray = new JsonArray(); + nestedArray.add('"'); + array.add(nestedArray); + JsonObject nestedObject = new JsonObject(); + nestedObject.addProperty("n\0", 1); + array.add(nestedObject); + assertThat(array.toString()).isEqualTo("[null,NaN,\"a\\u0000\",[\"\\\"\"],{\"n\\u0000\":1}]"); + } } diff --git a/gson/src/test/java/com/google/gson/JsonNullTest.java b/gson/src/test/java/com/google/gson/JsonNullTest.java index ca995b925b..d3c5b98cd6 100644 --- a/gson/src/test/java/com/google/gson/JsonNullTest.java +++ b/gson/src/test/java/com/google/gson/JsonNullTest.java @@ -43,4 +43,9 @@ public void testDeepCopy() { assertThat(a.deepCopy()).isSameInstanceAs(JsonNull.INSTANCE); assertThat(JsonNull.INSTANCE.deepCopy()).isSameInstanceAs(JsonNull.INSTANCE); } + + @Test + public void testToString() { + assertThat(JsonNull.INSTANCE.toString()).isEqualTo("null"); + } } diff --git a/gson/src/test/java/com/google/gson/JsonObjectTest.java b/gson/src/test/java/com/google/gson/JsonObjectTest.java index 353fa102d2..da6931c984 100644 --- a/gson/src/test/java/com/google/gson/JsonObjectTest.java +++ b/gson/src/test/java/com/google/gson/JsonObjectTest.java @@ -344,4 +344,21 @@ public void testEntrySet() { assertThat(new ArrayList<>(o.entrySet())).isEqualTo(new ArrayList<>(expectedEntriesQueue)); } } + + @Test + public void testToString() { + JsonObject object = new JsonObject(); + assertThat(object.toString()).isEqualTo("{}"); + + object.add("a", JsonNull.INSTANCE); + object.addProperty("b\0", Float.NaN); + JsonArray nestedArray = new JsonArray(); + nestedArray.add('"'); + object.add("c", nestedArray); + JsonObject nestedObject = new JsonObject(); + nestedObject.addProperty("n\0", 1); + object.add("d", nestedObject); + assertThat(object.toString()) + .isEqualTo("{\"a\":null,\"b\\u0000\":NaN,\"c\":[\"\\\"\"],\"d\":{\"n\\u0000\":1}}"); + } } diff --git a/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java b/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java index 523e1d8b3f..be1e122544 100644 --- a/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java +++ b/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java @@ -260,12 +260,34 @@ public void testDoubleEqualsBigDecimal() { } @Test - public void testValidJsonOnToString() { + public void testToString() { JsonPrimitive json = new JsonPrimitive("Some\nEscaped\nValue"); assertThat(json.toString()).isEqualTo("\"Some\\nEscaped\\nValue\""); + json = new JsonPrimitive(""); + assertThat(json.toString()).isEqualTo("\"\""); + json = new JsonPrimitive(new BigDecimal("1.333")); assertThat(json.toString()).isEqualTo("1.333"); + + // Preserves trailing 0 + json = new JsonPrimitive(new BigDecimal("1.0000")); + assertThat(json.toString()).isEqualTo("1.0000"); + + json = new JsonPrimitive(Float.NaN); + assertThat(json.toString()).isEqualTo("NaN"); + + json = new JsonPrimitive(Double.NEGATIVE_INFINITY); + assertThat(json.toString()).isEqualTo("-Infinity"); + + json = new JsonPrimitive('a'); + assertThat(json.toString()).isEqualTo("\"a\""); + + json = new JsonPrimitive('\0'); + assertThat(json.toString()).isEqualTo("\"\\u0000\""); + + json = new JsonPrimitive(true); + assertThat(json.toString()).isEqualTo("true"); } @Test diff --git a/metrics/pom.xml b/metrics/pom.xml index a31e6a5116..63fe091cf0 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -54,7 +54,7 @@ com.fasterxml.jackson.core jackson-databind - 2.16.1 + 2.17.0 com.google.caliper diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java index 4151b2abb5..601086c3c9 100644 --- a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java @@ -247,7 +247,7 @@ public void parse(char[] data, Document document) throws Exception { depth--; break; case FIELD_NAME: - jp.getCurrentName(); + jp.currentName(); break; case VALUE_STRING: jp.getText(); diff --git a/pom.xml b/pom.xml index f915fe0067..358fd05937 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ org.apache.maven.plugins maven-artifact-plugin - 3.5.0 + 3.5.1 @@ -213,7 +213,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.12.1 + 3.13.0 true true @@ -293,7 +293,7 @@ com.google.errorprone error_prone_core - 2.25.0 + 2.27.0 @@ -334,22 +334,22 @@ org.apache.maven.plugins maven-jar-plugin - 3.3.0 + 3.4.1 org.apache.maven.plugins maven-install-plugin - 3.1.1 + 3.1.2 org.apache.maven.plugins maven-source-plugin - 3.3.0 + 3.3.1 org.apache.maven.plugins maven-gpg-plugin - 3.1.0 + 3.2.4 org.apache.maven.plugins @@ -442,7 +442,7 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.18.5 + 0.21.1 diff --git a/proto/pom.xml b/proto/pom.xml index 33d7a4ebee..ee05445ac0 100644 --- a/proto/pom.xml +++ b/proto/pom.xml @@ -32,7 +32,7 @@ 2023-01-01T00:00:00Z - 3.25.3 + 4.26.1 @@ -58,7 +58,7 @@ com.google.guava guava - 33.0.0-jre + 33.1.0-jre diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java index c56ade3ad3..9be26bfe16 100644 --- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java @@ -28,7 +28,7 @@ import com.google.gson.protobuf.generated.Bag.OuterMessage; import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations; import com.google.gson.protobuf.generated.Bag.ProtoWithAnnotations.InnerMessage; -import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.GeneratedMessage; import org.junit.Before; import org.junit.Test; @@ -51,18 +51,18 @@ public void setUp() throws Exception { .addSerializedEnumValueExtension(Annotations.serializedValue); gson = new GsonBuilder() - .registerTypeHierarchyAdapter(GeneratedMessageV3.class, protoTypeAdapter.build()) + .registerTypeHierarchyAdapter(GeneratedMessage.class, protoTypeAdapter.build()) .create(); gsonWithEnumNumbers = new GsonBuilder() .registerTypeHierarchyAdapter( - GeneratedMessageV3.class, + GeneratedMessage.class, protoTypeAdapter.setEnumSerialization(EnumSerialization.NUMBER).build()) .create(); gsonWithLowerHyphen = new GsonBuilder() .registerTypeHierarchyAdapter( - GeneratedMessageV3.class, + GeneratedMessage.class, protoTypeAdapter .setFieldNameSerializationFormat( CaseFormat.LOWER_UNDERSCORE, CaseFormat.LOWER_HYPHEN) diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java index 1c3bc1c891..367aefc43b 100644 --- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java @@ -27,7 +27,7 @@ import com.google.gson.protobuf.generated.Bag.ProtoWithDifferentCaseFormat; import com.google.gson.protobuf.generated.Bag.ProtoWithRepeatedFields; import com.google.gson.protobuf.generated.Bag.SimpleProto; -import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.GeneratedMessage; import org.junit.Before; import org.junit.Test; @@ -45,7 +45,7 @@ public void setUp() throws Exception { gson = new GsonBuilder() .registerTypeHierarchyAdapter( - GeneratedMessageV3.class, + GeneratedMessage.class, ProtoTypeAdapter.newBuilder() .setEnumSerialization(EnumSerialization.NUMBER) .build()) @@ -53,7 +53,7 @@ public void setUp() throws Exception { upperCamelGson = new GsonBuilder() .registerTypeHierarchyAdapter( - GeneratedMessageV3.class, + GeneratedMessage.class, ProtoTypeAdapter.newBuilder() .setFieldNameSerializationFormat( CaseFormat.LOWER_UNDERSCORE, CaseFormat.UPPER_CAMEL) diff --git a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java index 57ab2df7ef..4bfd7241c4 100644 --- a/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java +++ b/proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java @@ -24,7 +24,7 @@ import com.google.gson.protobuf.ProtoTypeAdapter; import com.google.gson.protobuf.ProtoTypeAdapter.EnumSerialization; import com.google.gson.protobuf.generated.Bag.SimpleProto; -import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.GeneratedMessage; import org.junit.Before; import org.junit.Test; @@ -36,7 +36,7 @@ public void setUp() throws Exception { gson = new GsonBuilder() .registerTypeHierarchyAdapter( - GeneratedMessageV3.class, + GeneratedMessage.class, ProtoTypeAdapter.newBuilder() .setEnumSerialization(EnumSerialization.NUMBER) .build()) diff --git a/shrinker-test/pom.xml b/shrinker-test/pom.xml index dd76a4331b..50a7266405 100644 --- a/shrinker-test/pom.xml +++ b/shrinker-test/pom.xml @@ -114,7 +114,7 @@ com.guardsquare proguard-core - 9.1.1 + 9.1.3 @@ -145,7 +145,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.2 + 3.5.3 package @@ -218,7 +218,7 @@ but it appears that can be ignored --> com.android.tools r8 - 8.2.47 + 8.3.37