Compare commits

...

95 Commits

Author SHA1 Message Date
Johannes Frohnmeyer 7d030fad60
Fix build
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-09-19 20:14:48 +02:00
Johannes Frohnmeyer c46cbf85d4
Merge upstream 2023-09-19 17:22:29 +02:00
Johannes Frohnmeyer db89f98ed8
Add "configure" methods to Gson to apply JsonWriter/JsonReader configs 2023-09-19 16:29:07 +02:00
dependabot[bot] 78e4bdaede
Bump org.graalvm.buildtools:native-maven-plugin from 0.9.26 to 0.9.27 (#2489)
Bumps [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools) from 0.9.26 to 0.9.27.
- [Release notes](https://github.com/graalvm/native-build-tools/releases)
- [Commits](https://github.com/graalvm/native-build-tools/commits)

---
updated-dependencies:
- dependency-name: org.graalvm.buildtools:native-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 16:52:57 -07:00
dependabot[bot] 40bc86ef17
Bump org.apache.maven.plugins:maven-javadoc-plugin from 3.5.0 to 3.6.0 (#2490)
Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.5.0...maven-javadoc-plugin-3.6.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 16:52:36 -07:00
dependabot[bot] dda176cf80
Bump org.apache.maven.plugins:maven-enforcer-plugin from 3.4.0 to 3.4.1 (#2487)
Bumps [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/apache/maven-enforcer/releases)
- [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.4.0...enforcer-3.4.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-enforcer-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 07:36:57 -07:00
dependabot[bot] 27d1a66acf
Bump org.graalvm.buildtools:native-maven-plugin from 0.9.25 to 0.9.26 (#2486)
Bumps [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools) from 0.9.25 to 0.9.26.
- [Release notes](https://github.com/graalvm/native-build-tools/releases)
- [Commits](https://github.com/graalvm/native-build-tools/compare/0.9.25...0.9.26)

---
updated-dependencies:
- dependency-name: org.graalvm.buildtools:native-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-08 07:39:57 -07:00
Samuel Freilich ad5cd31498
Add shrinker-test examples with never-referenced constructors (#2478)
* Add shrinker-test examples with never-referenced constructors

R8 can do some optimizations that assume values of types whose
constructors are never referenced are `null`. (A bit confused why
the rules here are sufficient to avoid that optimization in the
case of a class whose sole constructor has arguments and is
unreferenced, since the keep rule only refers to `<init>()` instead
of `<init>(...)`. Does R8 have special behavior for `<init>()` where
attempting to keep that makes it assume a class is constructable even
if it doesn't have a no-args constructor?)

Also rename some test classes/names to refer to `NoArgs` instead of
`Default` constructors. The examples here care about whether a
no-argument constructor is present. While a no-argument
constructor (explicitly defined or not) is used by default in some
circumstances, the Java documentation consistently uses "default
constructor" to refer only to constructors whose implementation is
implicitly provided by the complier (all default constructors are
no-argument constructors, but not vice versa).

* Update shrinker-test/src/main/java/com/example/ClassWithNoArgsConstructor.java

Accept review suggestion

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update shrinker-test/src/main/java/com/example/NoSerializedNameMain.java

Accept review suggestion

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Further adjust test class/method names for consistency

* Update shrinker-test/src/main/java/com/example/NoSerializedNameMain.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

---------

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
2023-09-05 06:47:54 -07:00
dependabot[bot] 4654f5906c
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#2484)
Bumps [com.github.siom79.japicmp:japicmp-maven-plugin](https://github.com/siom79/japicmp) from 0.17.2 to 0.17.3.
- [Release notes](https://github.com/siom79/japicmp/releases)
- [Changelog](https://github.com/siom79/japicmp/blob/master/release.py)
- [Commits](https://github.com/siom79/japicmp/compare/japicmp-base-0.17.2...japicmp-base-0.17.3)

---
updated-dependencies:
- dependency-name: com.github.siom79.japicmp:japicmp-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-04 13:56:15 -07:00
Marcono1234 70bda4b9c9
Throw exception when calling `JsonWriter.name` outside JSON object (#2476)
Previous fix in 392cc65ff3 only covered writing
name as top level value, but not when trying to write name inside JSON array.

Removed `stackSize == 0` check from `JsonWriter.name` because that is done
already by `peek()` call.
2023-08-29 08:41:06 -07:00
dependabot[bot] ddc76ea4cc
Bump org.graalvm.buildtools:native-maven-plugin from 0.9.24 to 0.9.25 (#2482)
Bumps [org.graalvm.buildtools:native-maven-plugin](https://github.com/graalvm/native-build-tools) from 0.9.24 to 0.9.25.
- [Release notes](https://github.com/graalvm/native-build-tools/releases)
- [Commits](https://github.com/graalvm/native-build-tools/commits)

---
updated-dependencies:
- dependency-name: org.graalvm.buildtools:native-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 07:24:43 -07:00
dependabot[bot] 2432cf7afa
Bump org.apache.maven.plugins:maven-enforcer-plugin from 3.3.0 to 3.4.0 (#2477)
Bumps [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/apache/maven-enforcer/releases)
- [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.3.0...enforcer-3.4.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-enforcer-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-23 08:17:57 -07:00
Marcono1234 88fd6d1390
Improve `JsonAdapter` documentation and tests (#2442)
* Document how `JsonAdapter` creates adapter instances & add tests

* Extend `JsonAdapter.nullSafe()` documentation

* Improve test for JsonAdapter factory returning null

Existing test `JsonAdapterNullSafeTest` had misleading comments; while it
did in the end detect if null had not been handled correctly, that only
worked because the field `JsonAdapterFactory.recursiveCall` is static and
one test method therefore affected the state of the other test method.
If the test methods were run separately in different test runs, they would
not have detected if null was handled correctly, because the factory would
not have returned null.

* Extend JsonAdapter nullSafe test

* Extend test
2023-08-23 07:09:32 -07:00
Marcono1234 7ee5ad6cd1
Fix `Gson.getDelegateAdapter` not working properly for `JsonAdapter` (#2435)
* Fix `Gson.getDelegateAdapter` not working properly for `JsonAdapter`

* Address review feedback and add comments regarding thread-safety

* Revert InstanceCreator instance validation

* Disallow `null` as `skipPast`

* Avoid `equals` usage in `getDelegateAdapter` & minor other changes

Previously `getDelegateAdapter` called `factories.contains(skipPast)`,
but unlike the other comparisons which check for reference equality,
that would have used the `equals` method.
This could lead to spurious "GSON cannot serialize ..." exceptions
if two factory instances compared equal, but the one provided as
`skipPast` had not been registered yet.
2023-08-22 17:15:18 -07:00
Marcono1234 393db094dd
Add test for unused class to shrinker-test (#2455) 2023-08-22 16:31:56 -07:00
shivam-sehgal 392cc65ff3
Changes to ensure if no object is opened user won't be able to write property name (#2475)
* Changes to ensure if no object is opened user won't be able to add prop in the object

* review points

* spelling correction
2023-08-22 10:12:31 -07:00
Søren Gjesse 7b5629bcfc
Update ProGuard default shrinking rules (#2448)
* Update ProGuard default shrinking rules to correctly deal with classes without a no-args constructor

* Update test after changing default shrinking rules

* Adjust shrinker tests

* Update rules

* Addressed review comments

* Addressed more review comments

* Addressed more review comments

---------

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
2023-08-22 08:40:31 -07:00
Mustafa Ateş Uzun 1e7625b963
fix: ParseBenchmark json property typo (#2473) 2023-08-19 09:54:08 -07:00
Marcono1234 7b8ce2b9f7
Prevent `TypeToken` from capturing type variables (#2376)
* Prevent `TypeToken` from capturing type variables

* Use hyphen for term "type-safe"

Not completely sure if that is grammatically correct, but it might make the
text a bit easier to understand.

* Update Troubleshooting Guide URLs in tests from 'master' to 'main'

* Rename system property

* Simplify system property check
2023-08-14 14:26:05 -07:00
Éamonn McManus db4a58a417
Revert "Bump org.codehaus.plexus:plexus-utils from 3.5.1 to 4.0.0 (#2468)" (#2469)
This reverts commit 97d33f4a49.
2023-08-14 14:24:08 -07:00
dependabot[bot] 97d33f4a49
Bump org.codehaus.plexus:plexus-utils from 3.5.1 to 4.0.0 (#2468)
Bumps [org.codehaus.plexus:plexus-utils](https://github.com/codehaus-plexus/plexus-utils) from 3.5.1 to 4.0.0.
- [Release notes](https://github.com/codehaus-plexus/plexus-utils/releases)
- [Commits](https://github.com/codehaus-plexus/plexus-utils/compare/plexus-utils-3.5.1...plexus-utils-4.0.0)

---
updated-dependencies:
- dependency-name: org.codehaus.plexus:plexus-utils
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-14 06:56:52 -07:00
Marcono1234 77b566ced0
Fix graal-native-image-test module causing failure for 'Check API compatibility' (#2467)
The 'Check API compatibility' workflow runs `mvn install` to install the
previous version to the local Maven repository to compare it afterwards.
However this fails for the graal-native-image-test module because it does
not produce a JAR.

Therefore skip execution of the maven-install-plugin for that module.
2023-08-13 13:40:47 -07:00
Marcono1234 26b5a668b3
Add integration test for GraalVM Native Image (#2466)
* Add integration test for GraalVM Native Image

* Enable 'quickBuild' mode

Seems to improve build speed, and at least for the previous issue with
`RecordComponent` still causes test failures (as expected) if the fix
is reverted.
2023-08-13 08:44:00 -07:00
Éamonn McManus c5c1fbf9a1
Access `RecordComponent` via `Class.forName`. (#2465)
This should mean that GraalVM will understand the reflective lookup of its methods.
See [documentation](https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/Reflection/#automatic-detection).
2023-08-11 10:57:53 -07:00
Éamonn McManus cdbbee4e72
Bump com.google.errorprone:error_prone_core from 2.20.0 to 2.21.1 (#2463)
Suppress a couple of new Error Prone warnings.
2023-08-08 14:21:06 -07:00
dependabot[bot] e0cbbcc52b
Bump com.google.guava:guava-testlib from 32.1.1-jre to 32.1.2-jre (#2458)
Bumps [com.google.guava:guava-testlib](https://github.com/google/guava) from 32.1.1-jre to 32.1.2-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava-testlib
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 06:41:52 -07:00
dependabot[bot] f07fa7f487
Bump com.google.errorprone:error_prone_annotations from 2.20.0 to 2.21.1 (#2460)
Bumps [com.google.errorprone:error_prone_annotations](https://github.com/google/error-prone) from 2.20.0 to 2.21.1.
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.20.0...v2.21.1)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_annotations
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 06:39:17 -07:00
dependabot[bot] 55de17b90a
Bump com.google.guava:guava from 32.1.1-jre to 32.1.2-jre (#2461)
Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.1.1-jre to 32.1.2-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 06:36:25 -07:00
dependabot[bot] 83d44c0fa2
Bump com.android.tools:r8 from 8.0.40 to 8.1.56 (#2462)
Bumps com.android.tools:r8 from 8.0.40 to 8.1.56.

---
updated-dependencies:
- dependency-name: com.android.tools:r8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 06:35:47 -07:00
Marcono1234 f72824e2e4
Improve documentation about default `JsonReader` and `JsonWriter` behavior (#2341)
* Improve documentation about default `JsonWriter` behavior

* Revert accidental formatting change

* Document default JsonReader configuration & improve doc
2023-08-05 11:29:27 -07:00
Éamonn McManus b3dce2dc17
Strict mode for JSON parsing, contributed by @marten-voorberg. (#2437)
* Strict mode for JSON parsing (#2323)

* Feat #6: Add strict flag to Gson and GsonBuilder

* Test #2: Add failing tests for capitalized keywords

* Feat #2: JsonReader does not read (partially) capitalized keywords if strict mode is used

* Feat #3: Added implementation and tests for JSONReader not accepting specific escape sequence representing in strict mode

* Test #3: Simplify test cases by removing unnecessary array

* Feat #3: Improve error by including the illegal character

* Feat #5: JsonReader does not allow unespaced control flow characters in strict mode

* Test #5: Test unespaced control flow characters in strict mode

* Feat #4: Disallow espaced newline character in strict mode

* Test #4: Add tests for (dis)allowing newline character depensding on strictness

* Test #5: Test case for unescaped control char in non-strict mode

* Test #2: Simplify test cases

* Feat #13: Change leniency API to Strictness enum in JsonReader, Gson, and GsonBuilder

* Feat #15: Change JsonWriter API to also use Strictness

* Test #15: Test Strictness in JsonWriter API

* Doc #15: Add and update documentation for Strictness in JsonWriter API

* refactor #12: Fixed typos and empty catch brackets in tests

* refactor #12: Resolved importing wildcards, made some lines adhere to Google java style

* #5 Add test case for unescaped control characters

* Feat #5: add new lines to make JsonReader able to detect unescaped control characters (U+0000 through U+001F) and throw exceptions.

* Feat #5: add new lines to make JsonReader able to detect unescaped control characters (U+0000 through U+001F) and throw exceptions.

* Test #11: Added two tests for testing implementation of control character handling in strict mode and moved the implementation to nextQuotedValue

* Test #11: Added two tests for testing implementation of control character handling in strict mode and moved the implementation to nextQuotedValue

---------

Co-authored-by: LMC117 <2295699210@qq.com>
Co-authored-by: Marten Voorberg <martenvoorberg@gmail.com>

* Doc #17: Add and change javadoc of public methods

* Doc #17: Update JavaDoc in JsonReader and Strictness

* Doc #17: Update JavaDoc in Gson and GsonBuilder

* Test #34: Add tests for setting strictness through GsonBuilder

* Fix: Add Fix broken test

* Fix: Invalid JavaDoc in Gson.java

* Doc #17: update outdated javadoc

* #37: Resolve more PR feedback 

* Fix #37: Resolve various PR comments

* Fix #37: Resolve various PR comments

* Refactor #35: Refactor JsonReader#peekKeyword to reduce the amount of strictness checks (#39)

* Doc #40: Update JavaDoc based on PR feedback

* Doc #40: Update old RFC in GsonBuilder documentation

* Doc #40: Fix formatting error in JavaDoc

* Doc #40: Add tests for setting strictness and lenient to JsonReaderTest

* Test #43: Changed tests to make use of assertThrows

* test #43: Changed tests to make use of assertThrows as per feedback

* Test #43: Update JsonWriterTest#testStrictnessNull to use assertThrows

* Test #43: Update JsonWriterTest#testStrictnessNull to use assertThrows

* test #43: Resolve PR recommendations

* Test #43: Mini change to TC

* Test #43: Mini change to TC

---------

Co-authored-by: Marten Voorberg <martenvoorberg@gmail.com>

* doc #46: Resolved comments in main PR

* Feat #45: Change Gson.fromJson and Gson.toJson to be strict when the provided writer/reader is strict

* Fix #45: Small type

* Update gson/src/test/java/com/google/gson/stream/JsonReaderTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Fix #45: Resolve various comments by Marcono1234

* Update gson/src/main/java/com/google/gson/GsonBuilder.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Fix #45: Resolve various comments by Marcono1234

* Fix #45: Resolve various comments by eamonmcmanus

* Strictness mode follow-up

* Update Troubleshooting.md and Gson default lenient mode documentation

* Always use GSON strictness when set.

* Rename Strictness.DEFAULT to Strictness.LEGACY_STRICT

* Update JavaDoc with new strictness functionality

* Replace default with legacy strict for JsonReader javadoc

* Add JSONReader test cases for U2028 and U2029

* Refactor JSONReader#peekKeyWord() based on @eamonmcmanus's suggestion

* Deprecate setLenient in favor of setStrictness

---------

Co-authored-by: Carl Peterson <unknown>
Co-authored-by: Gustaf Johansson <gustajoh@kth.se>
Co-authored-by: gustajoh <58432871+gustajoh@users.noreply.github.com>
Co-authored-by: LMC117 <2295699210@qq.com>
Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Strictness follow-up (#2408)

* Strictness mode follow-up

- Remove mentions of `null` Gson strictness; this is an implementation detail
- Fix incorrect / outdated documentation
- Reduce links to RFC; if there is already a link to it in a previous sentence
  don't link to it again
- Extend and update tests
- Minor punctuation changes in documentation for consistency

* Deprecate `setLenient` methods

* `strictness2` fixes & improvements (#2456)

* Adjust ProGuard default rules and shrinking tests (#2420)

* Adjust ProGuard default rules and shrinking tests

* Adjust comment

* Add shrinking test for class without no-args constructor; improve docs

* Improve Unsafe mention in Troubleshooting Guide

* Improve comment for `-if class *`

* Bump com.google.guava:guava from 32.0.1-jre to 32.1.1-jre (#2444)

Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.0.1-jre to 32.1.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump com.google.guava:guava-testlib from 32.0.1-jre to 32.1.1-jre (#2443)

Bumps [com.google.guava:guava-testlib](https://github.com/google/guava) from 32.0.1-jre to 32.1.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava-testlib
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Support non-generic type for `TypeToken.getParameterized` for legacy reasons (#2447)

This partially restores the behavior before a589ef2008,
except that back then for a non-generic type a bogus `TypeToken(ParameterizedType)`
was created, whereas now a `TypeToken(Class)` is created instead.

* Fixed Typo in GsonBuilder.java (#2449)

* Make date-formatting tests less fragile with regular expressions. (#2450)

* Make date-formatting tests less fragile with regular expressions.

This is not great. We should really ensure that formatted dates are the same
regardless of JDK version. There is code that attempts to do that but it is not
really effective. So for now we fudge around the differences by using regular
expressions to paper over the differences.

* Temporarily add test-debugging code.

* Another attempt at debugging a test failure.

* Fix pattern in assertion.

* Modification in test cases (#2454)

* Fixed Typo in GsonBuilder.java

* Suggestions on Test cases

* Modified test cases using assertThrows method (JUnit)

* Update gson/src/test/java/com/google/gson/JsonArrayAsListTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/GsonTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonArrayAsListTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/TypeAdapterTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/TypeAdapterTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

---------

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Minor follow-up changes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: elevne <97422844+elevne@users.noreply.github.com>
Co-authored-by: Éamonn McManus <emcmanus@google.com>
Co-authored-by: Wonil <cwi5525@naver.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Marten <martenvoorberg@gmail.com>
Co-authored-by: Gustaf Johansson <gustajoh@kth.se>
Co-authored-by: gustajoh <58432871+gustajoh@users.noreply.github.com>
Co-authored-by: LMC117 <2295699210@qq.com>
Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: elevne <97422844+elevne@users.noreply.github.com>
Co-authored-by: Wonil <cwi5525@naver.com>
2023-07-30 09:20:32 -07:00
Wonil 3d241ca0a6
Modification in test cases (#2454)
* Fixed Typo in GsonBuilder.java

* Suggestions on Test cases

* Modified test cases using assertThrows method (JUnit)

* Update gson/src/test/java/com/google/gson/JsonArrayAsListTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/GsonTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonArrayAsListTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/JsonStreamParserTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/TypeAdapterTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/TypeAdapterTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

* Update gson/src/test/java/com/google/gson/ToNumberPolicyTest.java

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>

---------

Co-authored-by: Marcono1234 <Marcono1234@users.noreply.github.com>
2023-07-29 14:18:45 -07:00
Éamonn McManus 5055b62463
Make date-formatting tests less fragile with regular expressions. (#2450)
* Make date-formatting tests less fragile with regular expressions.

This is not great. We should really ensure that formatted dates are the same
regardless of JDK version. There is code that attempts to do that but it is not
really effective. So for now we fudge around the differences by using regular
expressions to paper over the differences.

* Temporarily add test-debugging code.

* Another attempt at debugging a test failure.

* Fix pattern in assertion.
2023-07-26 12:24:04 -07:00
elevne 5a87d80655
Fixed Typo in GsonBuilder.java (#2449) 2023-07-26 08:47:28 -07:00
Marcono1234 a38b757bc4
Support non-generic type for `TypeToken.getParameterized` for legacy reasons (#2447)
This partially restores the behavior before a589ef2008,
except that back then for a non-generic type a bogus `TypeToken(ParameterizedType)`
was created, whereas now a `TypeToken(Class)` is created instead.
2023-07-25 13:00:26 -07:00
dependabot[bot] 79ae239a49
Bump com.google.guava:guava-testlib from 32.0.1-jre to 32.1.1-jre (#2443)
Bumps [com.google.guava:guava-testlib](https://github.com/google/guava) from 32.0.1-jre to 32.1.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava-testlib
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 07:55:59 -07:00
dependabot[bot] 3d33a8621f
Bump com.google.guava:guava from 32.0.1-jre to 32.1.1-jre (#2444)
Bumps [com.google.guava:guava](https://github.com/google/guava) from 32.0.1-jre to 32.1.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 07:55:18 -07:00
Marcono1234 ecb9f8c8ad
Adjust ProGuard default rules and shrinking tests (#2420)
* Adjust ProGuard default rules and shrinking tests

* Adjust comment

* Add shrinking test for class without no-args constructor; improve docs

* Improve Unsafe mention in Troubleshooting Guide

* Improve comment for `-if class *`
2023-07-24 07:34:02 -07:00
Marcono1234 6d9c3566b7
Document minimum Android API level and add workflow to check compatibility (#2431) 2023-07-06 10:04:36 -07:00
Marcono1234 a589ef2008
Improve creation of `ParameterizedType` (#2375)
- Reject non-generic raw types for TypeToken.getParameterized
- Fix ParameterizedTypeImpl erroneously requiring owner type for types
  without owner
2023-06-23 15:24:23 -07:00
Éamonn McManus 9cf0f8d302
Update to latest Error Prone and fix some newly-flagged problems. (#2426) 2023-06-19 14:22:28 -07:00
dependabot[bot] 500ec501a7
Bump error_prone_annotations from 2.19.1 to 2.20.0 (#2421)
Bumps [error_prone_annotations](https://github.com/google/error-prone) from 2.19.1 to 2.20.0.
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.19.1...v2.20.0)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_annotations
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Éamonn McManus <emcmanus@google.com>
2023-06-19 09:16:18 -07:00
dependabot[bot] 5fa61b25b4
Bump maven-shade-plugin from 3.4.1 to 3.5.0 (#2423)
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.4.1...maven-shade-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Éamonn McManus <emcmanus@google.com>
2023-06-19 08:44:18 -07:00
dependabot[bot] fd8c8ac082
Bump truth from 1.1.4 to 1.1.5 (#2424)
Bumps [truth](https://github.com/google/truth) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/google/truth/releases)
- [Commits](https://github.com/google/truth/compare/v1.1.4...v1.1.5)

---
updated-dependencies:
- dependency-name: com.google.truth:truth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 08:40:32 -07:00
dependabot[bot] 8c523b8d2c
Bump guava from 32.0.0-jre to 32.0.1-jre (#2415)
Bumps [guava](https://github.com/google/guava) from 32.0.0-jre to 32.0.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 14:03:38 -07:00
dependabot[bot] 621a6a2d9a
Bump guava-testlib from 32.0.0-jre to 32.0.1-jre (#2416)
Bumps [guava-testlib](https://github.com/google/guava) from 32.0.0-jre to 32.0.1-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava-testlib
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 14:03:15 -07:00
Marcono1234 44217b9100
Rename branch references in GitHub workflow to 'main' (#2418)
* Rename branch references in GitHub workflow to 'main'

* Revert accidental branch reference renaming
2023-06-12 08:46:38 -07:00
Éamonn McManus a80bc24e04
Rename `master` to `main` everywhere. (#2410) 2023-06-07 11:02:13 -07:00
dependabot[bot] e7f85dff8a
Bump maven-surefire-plugin from 3.1.0 to 3.1.2 (#2411)
Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.0...surefire-3.1.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-07 08:17:46 -07:00
dependabot[bot] 144626ddd9
Bump maven-failsafe-plugin from 3.1.0 to 3.1.2 (#2412)
Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.0...surefire-3.1.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-failsafe-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-07 08:16:59 -07:00
dependabot[bot] cf50a5aaf1
Bump maven-release-plugin from 3.0.0 to 3.0.1 (#2409)
Bumps [maven-release-plugin](https://github.com/apache/maven-release) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/apache/maven-release/releases)
- [Commits](https://github.com/apache/maven-release/compare/maven-release-3.0.0...maven-release-3.0.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-release-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 12:55:48 -07:00
dependabot[bot] 2d7cc2e9f4
Bump jackson-databind from 2.15.1 to 2.15.2 (#2403)
Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.15.1 to 2.15.2.
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-31 09:45:36 -07:00
dependabot[bot] 6292eafcdf
Bump truth from 1.1.3 to 1.1.4 (#2404)
Bumps [truth](https://github.com/google/truth) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/google/truth/releases)
- [Commits](https://github.com/google/truth/commits/v1.1.4)

---
updated-dependencies:
- dependency-name: com.google.truth:truth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-31 09:45:08 -07:00
Marcono1234 481ac9b82c
Use non-`null` `FormattingStyle`; configure space after separator (#2345)
* Use non-`null` `FormattingStyle`; configure space after separator

* Improve Javadoc and tests

* Rename to plural separator*s*

* Add explicit tests for default formatting styles

---------

Co-authored-by: Éamonn McManus <emcmanus@google.com>
2023-05-30 16:32:22 -07:00
dependabot[bot] d3e17587fe
Bump guava from 31.1-jre to 32.0.0-jre (#2399)
Bumps [guava](https://github.com/google/guava) from 31.1-jre to 32.0.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 15:42:32 -07:00
dependabot[bot] a8a928ee51
Bump guava-testlib from 31.1-jre to 32.0.0-jre (#2400)
Bumps [guava-testlib](https://github.com/google/guava) from 31.1-jre to 32.0.0-jre.
- [Release notes](https://github.com/google/guava/releases)
- [Commits](https://github.com/google/guava/commits)

---
updated-dependencies:
- dependency-name: com.google.guava:guava-testlib
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-30 15:42:06 -07:00
Marcono1234 43396e45fd
Add ProGuard / R8 integration tests & add default ProGuard rules (#2397)
* Add code shrinking tools integration test

* Keep no-args constructor of classes usable with JsonAdapter

* Add library ProGuard rules for Gson

They are automatically applied for all users of Gson, see
https://developer.android.com/build/shrink-code#configuration-files

* Skip japicmp-maven-plugin for shrinker-test

* Add more tests for JsonAdapter, add tests for generic classes

* Extend default constructor test

* Add Troubleshooting Guide entry for TypeToken
2023-05-28 12:24:05 -07:00
Marcono1234 4c65a82871
Remove accidental invisible characters from Troubleshooting Guide (#2395)
Accidentally contained U+200C (Zero Width Non-Joiner) followed by U+200B (Zero Width Space)
2023-05-24 14:41:26 -07:00
dependabot[bot] ed8ca46aa8
Bump maven-source-plugin from 3.2.1 to 3.3.0 (#2392)
Bumps [maven-source-plugin](https://github.com/apache/maven-source-plugin) from 3.2.1 to 3.3.0.
- [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-3.2.1...maven-source-plugin-3.3.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-source-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 07:44:37 -07:00
dependabot[bot] 6e39d1f1d3
Bump jackson-databind from 2.15.0 to 2.15.1 (#2391)
Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.15.0 to 2.15.1.
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-17 07:01:17 -07:00
Marcono1234 a9bd30b577
Enable Error Prone HidingField bug pattern again (#2390)
Bug which required disabling this bug pattern was fixed in 2.19.1 and
Error Prone dependency was updated to that version in commit
3c4b072100
2023-05-11 13:18:09 -07:00
dependabot[bot] 3c4b072100
Bump error_prone_core from 2.19.0 to 2.19.1 (#2388)
Bumps [error_prone_core](https://github.com/google/error-prone) from 2.19.0 to 2.19.1.
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.19.0...v2.19.1)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-11 09:43:50 -07:00
dependabot[bot] eae63f92fb
Bump error_prone_annotations from 2.19.0 to 2.19.1 (#2389)
Bumps [error_prone_annotations](https://github.com/google/error-prone) from 2.19.0 to 2.19.1.
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.19.0...v2.19.1)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_annotations
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-11 09:43:22 -07:00
Éamonn McManus 49dbc6f072
Update Error Prone, bypass a bug in it, fix some problems it flags (#2387) 2023-05-09 10:28:39 -07:00
dependabot[bot] b330ad9790
Bump error_prone_annotations from 2.18.0 to 2.19.0 (#2386)
Bumps [error_prone_annotations](https://github.com/google/error-prone) from 2.18.0 to 2.19.0.
- [Release notes](https://github.com/google/error-prone/releases)
- [Commits](https://github.com/google/error-prone/compare/v2.18.0...v2.19.0)

---
updated-dependencies:
- dependency-name: com.google.errorprone:error_prone_annotations
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-09 06:44:34 -07:00
dependabot[bot] 1339b50629
Bump maven-surefire-plugin from 3.0.0 to 3.1.0 (#2383)
Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0...surefire-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Éamonn McManus <emcmanus@google.com>
2023-05-08 12:17:29 -07:00
dependabot[bot] 5fffd5aca1
Bump maven-gpg-plugin from 3.0.1 to 3.1.0 (#2384)
Bumps [maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 3.0.1 to 3.1.0.
- [Commits](https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-3.0.1...maven-gpg-plugin-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-gpg-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-08 12:11:43 -07:00
dependabot[bot] 8333ccac62
Bump moditect-maven-plugin from 1.0.0.RC3 to 1.0.0.Final (#2381)
Bumps [moditect-maven-plugin](https://github.com/moditect/moditect) from 1.0.0.RC3 to 1.0.0.Final.
- [Release notes](https://github.com/moditect/moditect/releases)
- [Commits](https://github.com/moditect/moditect/compare/1.0.0.RC3...1.0.0.Final)

---
updated-dependencies:
- dependency-name: org.moditect:moditect-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-04 06:23:28 -07:00
dependabot[bot] 55cf9fa941
Bump jackson-databind from 2.14.2 to 2.15.0 (#2377)
Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.14.2 to 2.15.0.
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-24 07:27:14 -07:00
Marcono1234 cbad1aa79f
Link to troubleshooting guide from exception messages (#2357)
* Link to troubleshooting guide from exception messages

* Add examples to troubleshooting guide

* Use proper anchor names for troubleshooting guide
2023-04-15 13:36:26 -07:00
Avi Mathur 1717fd62cf
Remove the comment strings " json is " from the UserGuide.md (#2374)
* removed ' json is ' from the UserGuide.md

* few more changes in UserGuide.md
2023-04-15 11:18:21 -07:00
Éamonn McManus 37ed0fcbd7
Address review comments from @Marcono1234. (#2372) 2023-04-10 13:33:47 -07:00
Éamonn McManus c1da2d7070
Add `@CanIgnoreReturnValue` as appropriate to Gson methods. (#2369)
This annotation indicates that return value of the annotated method does
not need to be used. If it is _not_ present on a non-void method, and if
Error Prone's `CheckReturnValue` is active, then calling the method
without using the result is an error. However, we are not enabling
`CheckReturnValue` by default here.

Also update some code that does ignore return values, so that the
returned value is used, if only by assigning it to an unused variable.
2023-04-10 10:50:25 -07:00
dependabot[bot] b43ccee889
Bump maven-surefire-plugin from 3.0.0-M9 to 3.0.0 (#2342)
Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M9 to 3.0.0.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M9...surefire-3.0.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-08 10:05:20 -07:00
dependabot[bot] f1b4a71de5
Bump maven-enforcer-plugin from 3.2.1 to 3.3.0 (#2366)
Bumps [maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.2.1 to 3.3.0.
- [Release notes](https://github.com/apache/maven-enforcer/releases)
- [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.2.1...enforcer-3.3.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-enforcer-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-05 09:06:16 -07:00
Maicol 051cb43fd9
Fixes outdated URL (`ISO8601Utils.java`) (#2363)
* Fixes comments

Fixes the javadoc and the link to Jackson databind ISO8601Utils.java

* Updates outdated URL in UtcDateTypeAdapter
2023-04-01 13:59:51 -07:00
TuZhiQiang 3adead6ab4
Fix incorrect sample code in User Guide (#2362) 2023-03-30 10:32:09 -07:00
dependabot[bot] 1d9d9774be
Bump maven-resources-plugin from 3.3.0 to 3.3.1 (#2360)
Bumps [maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.3.0 to 3.3.1.
- [Release notes](https://github.com/apache/maven-resources-plugin/releases)
- [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.3.0...maven-resources-plugin-3.3.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-resources-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 08:28:24 -07:00
Marcono1234 c34b9ff79a
Fix incorrect inherited URLs in `pom.xml` (#2351)
Previously Maven appended the artifact ID of the modules which lead for
example for the `gson` module to the incorrect URL https://github.com/google/gson/gson

This can be checked with `mvn help:effective-pom`
2023-03-23 13:36:57 -07:00
Marcono1234 7f303b5db4
Mention creation of GitHub release in `ReleaseProcess.md` (#2349)
* Mention creation of GitHub release in `ReleaseProcess.md`

* Link to GitHub releases from README
2023-03-21 15:26:57 -07:00
dependabot[bot] 75bf3b4157
Bump maven-release-plugin from 3.0.0-M7 to 3.0.0 (#2347)
Bumps [maven-release-plugin](https://github.com/apache/maven-release) from 3.0.0-M7 to 3.0.0.
- [Release notes](https://github.com/apache/maven-release/releases)
- [Commits](https://github.com/apache/maven-release/compare/maven-release-3.0.0-M7...maven-release-3.0.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-release-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 07:57:58 -07:00
Marcono1234 26229d33d8
FormattingStyle follow-up (#2327)
* FormattingStyle follow-up

* Add links to FormattingStyle

* Use Truth for FormattingStyleTest

* Reduce pull request scope to Javadoc and minor code changes
2023-03-19 09:15:35 -07:00
Marcono1234 6eddbf3031
Make dependency on Error Prone Annotations non-optional (#2346) 2023-03-19 08:54:31 -07:00
dependabot[bot] 1da826dc6c
Bump moditect-maven-plugin from 1.0.0.RC2 to 1.0.0.RC3 (#2340)
Bumps [moditect-maven-plugin](https://github.com/moditect/moditect) from 1.0.0.RC2 to 1.0.0.RC3.
- [Release notes](https://github.com/moditect/moditect/releases)
- [Commits](https://github.com/moditect/moditect/compare/1.0.0.CR2...1.0.0.RC3)

---
updated-dependencies:
- dependency-name: org.moditect:moditect-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-08 06:45:07 -08:00
Marcono1234 2f7be29a30
Specify Error Prone JVM args with `.mvn/jvm.config` (#2339)
* Specify Error Prone JVM args with `.mvn/jvm.config`

This allows avoiding fork mode for compilation, which:
- might slightly reduce compilation time
- guarantees that all compiler diagnostics are properly reported

* Combine enforcer executions
2023-03-07 13:39:05 -08:00
Marcono1234 f467ec20c3
Fix incorrect comment in `pom.xml` (#2338)
Accidentally referred to wrong JDK bug report
2023-03-06 12:29:09 -08:00
Maicol 85ebaf7c35
Fix #2334 (#2337)
* Fix #2334

This commit replaces the `NumberFormatException` with `MalformedJsonException` in the `JsonReader#readEscapeCharacter()` and also fixes the tests.

* Removes white-space
2023-03-06 08:24:09 -08:00
Marcono1234 0adcdc80d5
Fail Maven build on warnings (#2335)
* Fail Maven build on warnings

* Fix configuration parameter name

For the Javadoc plugin it is "warnings" (plural) instead of "warning"
2023-03-06 07:33:50 -08:00
Marcono1234 ef35a34a60
Enforce build using JDK >= 11 (#2333)
Building already requires JDK >= 11, but previously using a lower JDK
version such as JDK 8 would lead to cryptic build errors because some
compiler flags are unsupported.
Using the Maven Enforcer Plugin makes sure that the build fails early
with a somewhat better to understand error message.
2023-03-04 15:40:49 -08:00
dependabot[bot] 19983737ae
Bump japicmp-maven-plugin from 0.17.1 to 0.17.2 (#2331)
Bumps [japicmp-maven-plugin](https://github.com/siom79/japicmp) from 0.17.1 to 0.17.2.
- [Release notes](https://github.com/siom79/japicmp/releases)
- [Changelog](https://github.com/siom79/japicmp/blob/master/release.py)
- [Commits](https://github.com/siom79/japicmp/compare/japicmp-base-0.17.1...japicmp-base-0.17.2)

---
updated-dependencies:
- dependency-name: com.github.siom79.japicmp:japicmp-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-02 10:15:13 -08:00
Maicol dc20b7561a
Fix error prone warns (#2320)
* Adds `@SuppressWarnings("NarrowingCompoundAssignment")`

* Adds `@SuppressWarnings("TypeParameterUnusedInFormals")`

* Adds `@SuppressWarnings("JavaUtilDate")`

* Adds a limit to `String.split()`

* Add `error_prone_annotations` to the `pom.xml`

* Adds `@InlineMe(...)` to deprecated methods

* Adds `@SuppressWarnings("ImmutableEnumChecker")`

* Adds `@SuppressWarnings("ModifiedButNotUsed")`

* Adds `@SuppressWarnings("MixedMutabilityReturnType")`

* Removes an unused import

* Adds `requires` to `module-info.java`

* Adds ErrorProne `link` into `pom.xml`

* Remove unused imports

Removed from:
- ParseBenchmark

* Adds `@SuppressWarnings("EqualsGetClass")`

* Excludes from `proto` just the generated code.

Replaces `.*proto.*` with `.*/generated-test-sources/protobuf/.*` in such way will be excluded just the generated code and not the whole `proto` directory

* Removes an unused variable

Removes the `descriptor` variable because is unused.

* Fixes the `BadImport` warn into `ProtosWithAnnotationsTest`

* Fixes the `BadImport` warns into `ProtosWithAnnotationsTest`

* Enables ErrorProne in `gson/src/test.*`

Removes the `gson/src/test.*` path from the `-XepExcludedPaths` parameter of the ErrorProne plugin

* Fixes `UnusedVariable` warns

This commit fix all `UnusedVariable` warns given by ErrorProne in the `gson/src/test.*` path.

Some field is annotated with `@Keep` that means is used by reflection.
In some other case the record is annotated with `@SuppressWarnings("unused")`

* Fixes `JavaUtilDate` warns

This commit fix all `JavaUtilDate` warns given by ErrorProne in the `gson/src/test.*` path.

Classes/Methods are annotated with `@SuppressWarnings("JavaUtilDate")` because it's not possible use differente Date API.

* Fixes `EqualsGetClass` warns

This commit fix all `EqualsGetClass` warns given by ErrorProne in the `gson/src/test.*` path.

I have rewrite the `equals()` methods to use `instanceof`

* Replaces pattern matching for instanceof with casts

* Fixes `JdkObsolete` warns

This commit fix all `JdkObsolete` warns given by ErrorProne in the `gson/src/test.*` path.

In some cases I have replaced the obsolete JDK classes with the newest alternatives.  In other cases, I have added the `@SuppressWarnings("JdkObsolete")`

* Fixes `ClassCanBeStatic` warns

This commit fix all `ClassCanBeStatic` warns given by ErrorProne in the `gson/src/test.*` path.

I have added the `static` keyword, or I have added `@SuppressWarnings("ClassCanBeStatic")`

* Fixes `UndefinedEquals` warns

This commit fix all `UndefinedEquals` warns given by ErrorProne in the `gson/src/test.*` path.

I have added `@SuppressWarnings("UndefinedEquals")` or fixed the asserts

Note: In this commit I have also renamed a test from `testRawCollectionDeserializationNotAlllowed` to `testRawCollectionDeserializationNotAllowed`

* Fixes `GetClassOnEnum` warns

This commit fix all `GetClassOnEnum` warns given by ErrorProne in the `gson/src/test.*` path.

I have replaced the `.getClass()` with `.getDeclaringClass()`

* Fixes `ImmutableEnumChecker` warns

This commit fix all `ImmutableEnumChecker` warns given by ErrorProne in the `gson/src/test.*` path.

I have added the `final` keyword, or I have added the `@SuppressWarnings("ImmutableEnumChecker")` annotation

* Fixes `StaticAssignmentOfThrowable` warns

This commit fix all `StaticAssignmentOfThrowable` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `AssertionFailureIgnored` warns

This commit fix all `AssertionFailureIgnored` warns given by ErrorProne in the `gson/src/test.*` path.

I have added the `@SuppressWarnings("AssertionFailureIgnored")` annotation

* Fixes `ModifiedButNotUsed` warns

This commit fix all `ModifiedButNotUsed` warns given by ErrorProne in the `gson/src/test.*` path.

I have added the `@SuppressWarnings("ModifiedButNotUsed")` annotation

* Fixes `MissingSummary` warns

This commit fix all `MissingSummary` warns given by ErrorProne in the `gson/src/test.*` path.

I have remove the Javadoc `@author`

* Fixes `FloatingPointLiteralPrecision` warns

This commit fix all `FloatingPointLiteralPrecision` warns given by ErrorProne in the `gson/src/test.*` path.

I have added the `@SuppressWarnings("FloatingPointLiteralPrecision")` annotation

* Fixes `StringSplitter` warns

This commit fix all `StringSplitter` warns given by ErrorProne in the `gson/src/test.*` path.

I have replaced the `String.split(...)` with `Splitter`

* Fixes `EmptyCatch` warns

This commit fix all `EmptyCatch` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `UnicodeEscape` warns

This commit fix all `UnicodeEscape` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `EmptyBlockTag` warns

This commit fix all `EmptyBlockTag` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `LongFloatConversion` warns

This commit fix all `LongFloatConversion` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `LongDoubleConversion` warns

This commit fix all `LongDoubleConversion` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `TruthAssertExpected` warns

This commit fix all `TruthAssertExpected` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `UnusedMethod` warns

This commit fix all `UnusedMethod` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `UnusedTypeParameter` warns

This commit fix all `UnusedTypeParameter` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `CatchFail` warns

This commit fix all `CatchFail` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `MathAbsoluteNegative` warns

This commit fix all `MathAbsoluteNegative` warns given by ErrorProne in the `gson/src/test.*` path.

* Fixes `LoopOverCharArray` warns

This commit fix all `LoopOverCharArray` warns given by ErrorProne in the `gson/src/test.*` path.

`toCharArray` allocates a new array, using `charAt` is more efficient

* Fixes `HidingField` warns

This commit fix all `HidingField` warns given by ErrorProne in the `gson/src/test.*` path.

* Implements code review feedback

* Implements code review feedback

This commit implements some other code review feedback

* Enable ErrorProne in `extra`

Thi commit removes the `.*extras/src/test.*` path from the `-XepExcludedPaths` parameter of ErrorProne.

* Fix the `JavaUtilDate` warns

This commit fix all `JavaUtilDate` warns given by ErrorProne in the `extras/src/test.*` path.

* Implements code review feedback

* Removes redundant new-line

* Implements code review feedback

* Adds `JDK11` to run test with `--release 11`

* Revert "Adds `JDK11` to run test with `--release 11`"

This reverts commit a7cca386098ae847a10a31c09c3ab9b11eee5920.
2023-03-01 14:23:27 -08:00
dependabot[bot] bfbbd0dde8
Bump maven-compiler-plugin from 3.10.1 to 3.11.0 (#2330)
Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.10.1 to 3.11.0.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.10.1...maven-compiler-plugin-3.11.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-compiler-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-28 15:44:43 -08:00
sembseth 105f28ffe6
BoundField.write memory optimization (#2325)
* BoundField.write memory optimization

Declare and initialize the type adapter used for writing BoundFields outside of the anonymous class to ensure that a new TypeAdapterRuntimeTypeWrapper is not constructed each time a BoundField is written. This type adapter is only initialized if the BoundField will be used for serialization.

* Avoid confusing nullness-analysis tools
2023-02-28 14:46:05 -08:00
Maicol 9f46977d57
Adds the `<maven.compiler.testRelease>11</maven.compiler.testRelease>` (#2324)
* Adds `JDK11` to run test with `--release 11`

This commit add a new profile `JDK11` that with a JDK <= 11 apply the property `<maven.compiler.testRelease>11</maven.compiler.testRelease>`

* Removes the `JDK11` profile & Adds `testRelease` property
2023-02-27 15:34:21 -08:00
Marcono1234 850e977773
Add pull request template (#2326) 2023-02-27 13:52:57 -08:00
122 changed files with 4044 additions and 1783 deletions

10
.mvn/jvm.config Normal file
View File

@ -0,0 +1,10 @@
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED

View File

@ -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

View File

@ -81,11 +81,18 @@ When this module is present, Gson can use the `Unsafe` class to create instances
However, care should be taken when relying on this. `Unsafe` is not available in all environments and its usage has some pitfalls,
see [`GsonBuilder.disableJdkUnsafe()`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#disableJdkUnsafe()).
#### Minimum Android API level
- Gson 2.11.0 and newer: API level 21
- Gson 2.10.1 and older: API level 19
Older Gson versions may also support lower API levels, however this has not been verified.
### Documentation
* [API Javadoc](https://www.javadoc.io/doc/com.google.code.gson/gson): Documentation for the current release
* [User guide](UserGuide.md): This guide contains examples on how to use Gson in your code
* [Troubleshooting guide](Troubleshooting.md): Describes how to solve common issues when using Gson
* [Change log](CHANGELOG.md): Changes in the recent versions
* [Releases and change log](https://github.com/google/gson/releases): Latest releases and changes in these versions; for older releases see [`CHANGELOG.md`](CHANGELOG.md)
* [Design document](GsonDesignDocument.md): This document discusses issues we faced while designing Gson. It also includes a comparison of Gson with other Java libraries that can be used for Json conversion
Please use the ['gson' tag on StackOverflow](https://stackoverflow.com/questions/tagged/gson) or the [google-gson Google group](https://groups.google.com/group/google-gson) to discuss Gson or to post questions.
@ -95,6 +102,22 @@ Please use the ['gson' tag on StackOverflow](https://stackoverflow.com/questions
* [Gson Tutorial Series](https://futurestud.io/tutorials/gson-getting-started-with-java-json-serialization-deserialization) by `Future Studio`
* [Gson API Report](https://abi-laboratory.pro/java/tracker/timeline/gson/)
### Building
Gson uses Maven to build the project:
```
mvn clean verify
```
JDK 11 or newer is required for building, JDK 17 is recommended.
### Contributing
See the [contributing guide](https://github.com/google/.github/blob/master/CONTRIBUTING.md).
Please perform a quick search to check if there are already existing issues or pull requests related to your contribution.
Keep in mind that Gson is in maintenance mode. 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.
### License
Gson is released under the [Apache 2.0 license](LICENSE).

View File

@ -13,7 +13,7 @@ The following is a step-by-step procedure for releasing a new version of Google-
1. [Log in to Nexus repository manager](https://oss.sonatype.org/index.html#welcome) at Sonatype and close the staging repository for Gson.
1. Download and sanity check all downloads. Do not skip this step! Once you release the staging repository, there is no going back. It will get synced with Maven Central and you will not be able to update or delete anything. Your only recourse will be to release a new version of Gson and hope that no one uses the old one.
1. Release the staging repository for Gson. Gson will now get synced to Maven Central with-in the next hour. For issues consult [Sonatype Guide](https://central.sonatype.org/publish/release/).
1. Update [Gson Changelog](CHANGELOG.md). Also, look at all bugs that were fixed and add a few lines describing what changed in the release.
1. Create a [GitHub release](https://github.com/google/gson/releases) for the new version. You can let GitHub [automatically generate the description for the release](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes), but you should edit it manually to point out the most important changes and potentially incompatible changes.
1. Update version references in (version might be referenced multiple times):
- [`README.md`](README.md)
- [`UserGuide.md`](UserGuide.md)

View File

@ -2,7 +2,10 @@
This guide describes how to troubleshoot common issues when using Gson.
## `ClassCastException` when using deserialized object
<!-- The '<a id="..."></a>' anchors below are used to create stable links; don't remove or rename them -->
<!-- Use only lowercase IDs, GitHub seems to not support uppercase IDs, see also https://github.com/orgs/community/discussions/50962 -->
## <a id="class-cast-exception"></a> `ClassCastException` when using deserialized object
**Symptom:** `ClassCastException` is thrown when accessing an object deserialized by Gson
@ -14,9 +17,9 @@ This guide describes how to troubleshoot common issues when using Gson.
See the [user guide](UserGuide.md#collections-examples) for more information.
- 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)).
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<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)`.
- 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](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.
## `InaccessibleObjectException`: 'module ... does not "opens ..." to unnamed module'
## <a id="reflection-inaccessible"></a> `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
@ -30,7 +33,7 @@ When no built-in adapter for a type exists and no custom adapter has been regist
If 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`.
## `InaccessibleObjectException`: 'module ... does not "opens ..." to module com.google.gson'
## <a id="reflection-inaccessible-to-module-gson"></a> `InaccessibleObjectException`: 'module ... does not "opens ..." to module com.google.gson'
**Symptom:** An exception with a message in the form 'module ... does not "opens ..." to module com.google.gson' is thrown
@ -51,7 +54,7 @@ module mymodule {
Or in case this occurs for a field in one of your classes which you did not actually want to serialize or deserialize in the first place, you can exclude that field, see the [user guide](UserGuide.md#excluding-fields-from-serialization-and-deserialization).
## Android app not working in Release mode; random property names
## <a id="android-app-random-names"></a> Android app not working in Release mode; random property names
**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`, ...
@ -59,7 +62,7 @@ Or in case this occurs for a field in one of your classes which you did not actu
**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.
## Android app unable to parse JSON after app update
## <a id="android-app-broken-after-app-update"></a> Android app unable to parse JSON after app update
**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
@ -71,7 +74,7 @@ If you want to preserve backward compatibility for you app you can use [`@Serial
Normally 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.
## Default field values not present after deserialization
## <a id="default-field-values-missing"></a> Default field values not present after deserialization
**Symptom:** You have assign default values to fields but after deserialization the fields have their standard value (such as `null` or `0`)
@ -84,7 +87,7 @@ Normally ProGuard and R8 produce a mapping file, this makes it easier to find ou
Otherwise 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.
## `null` values for anonymous and local classes
## <a id="anonymous-local-null"></a> `null` values for anonymous and local classes
**Symptom:** Objects of a class are always serialized as JSON `null` / always deserialized as Java `null`
@ -97,7 +100,7 @@ Notes:
- "double brace-initialization" also creates anonymous classes
- Local record classes (feature added in Java 16) are supported by Gson and are not affected by this
## Map keys having unexpected format in JSON
## <a id="map-key-wrong-json"></a> Map keys having unexpected format in JSON
**Symptom:** JSON output for `Map` keys is unexpected / cannot be deserialized again
@ -105,15 +108,32 @@ Notes:
**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.
## Parsing JSON fails with `MalformedJsonException`
## <a id="malformed-json"></a> Parsing JSON fails with `MalformedJsonException`
**Symptom:** JSON parsing fails with `MalformedJsonException`
**Reason:** The JSON data is actually malformed
**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/).
**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/).
## Integral JSON number is parsed as `double`
For example, let's assume you want to deserialize the following JSON data:
```json
{
"languages": [
"English",
"French",
]
}
```
This will fail with an exception similar to this one: `MalformedJsonException: Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 5 column 4 path $.languages[2]`
The problem here is the trailing comma (`,`) after `"French"`, trailing commas are not allowed by the JSON specification. The location information "line 5 column 4" points to the `]` in the JSON data (with some slight inaccuracies) because Gson expected another value after `,` instead of the closing `]`. The JSONPath `$.languages[2]` in the exception message also points there: `$.` refers to the root object, `languages` refers to its member of that name and `[2]` refers to the (missing) third value in the JSON array value of that member (numbering starts at 0, so it is `[2]` instead of `[3]`).
The proper solution here is to fix the malformed JSON data.
To spot syntax errors in the JSON data easily you can open it in an editor with support for JSON, for example Visual Studio Code. It will highlight within the JSON data the error location and show why the JSON data is considered invalid.
## <a id="number-parsed-as-double"></a> Integral JSON number is parsed as `double`
**Symptom:** JSON data contains an integral number such as `45` but Gson returns it as `double`
@ -121,17 +141,20 @@ Notes:
**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
## Malformed JSON not rejected
## <a id="default-lenient"></a> Malformed JSON not rejected
**Symptom:** Gson parses malformed JSON without throwing any exceptions
**Reason:** Due to legacy reasons Gson performs parsing by default in lenient mode
**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"
**Solution:** If you are using Gson 2.11.0 or newer, call [`GsonBuilder.setStrictness`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#setStrictness(com.google.gson.Strictness)),
[`JsonReader.setStrictness`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonReader.html#setStrictness(com.google.gson.Strictness))
and [`JsonWriter.setStrictness`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/stream/JsonWriter.html#setStrictness(com.google.gson.Strictness))
with `Strictness.STRICT` to overwrite the default lenient behavior of `Gson` and make these classes strictly adhere to the JSON specification.
Otherwise if you are using an older Gson version, see the [`Gson` class documentation](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/Gson.html#default-lenient)
section "JSON Strictness handling" for alternative solutions.
Note: 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.
## `IllegalStateException`: "Expected ... but was ..."
## <a id="unexpected-json-structure"></a> `IllegalStateException`: "Expected ... but was ..."
**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was ..." is thrown
@ -139,13 +162,36 @@ Note: Even in non-lenient mode Gson deviates slightly from the JSON specificatio
**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/).
## `IllegalStateException`: "Expected ... but was NULL"
For example, let's assume you have the following Java class:
```java
class WebPage {
String languages;
}
```
And you want to deserialize the following JSON data:
```json
{
"languages": ["English", "French"]
}
```
This will fail with an exception similar to this one: `IllegalStateException: Expected a string but was BEGIN_ARRAY at line 2 column 17 path $.languages`
This means Gson expected a JSON string value but found the beginning of a JSON array (`[`). The location information "line 2 column 17" points to the `[` in the JSON data (with some slight inaccuracies), so does the JSONPath `$.languages` in the exception message. It refers to the `languages` member of the root object (`$.`).
The solution here is to change in the `WebPage` class the field `String languages` to `List<String> languages`.
## <a id="adapter-not-null-safe"></a> `IllegalStateException`: "Expected ... but was NULL"
**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was NULL" is thrown
**Reason:** You have written a custom `TypeAdapter` which does not properly handle a JSON null value
**Reason:**
**Solution:** Add code similar to the following at the beginning of the `read` method of your adapter:
- A built-in adapter does not support JSON null values
- 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:
```java
@Override
@ -154,14 +200,14 @@ public MyClass read(JsonReader in) throws IOException {
in.nextNull();
return null;
}
...
}
```
Alternatively 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.
## Properties missing in JSON
## <a id="serialize-nulls"></a> Properties missing in JSON
**Symptom:** Properties are missing in the JSON output
@ -171,7 +217,7 @@ Alternatively you can call [`nullSafe()`](https://www.javadoc.io/doc/com.google.
Note: 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).
## JSON output changes for newer Android versions
## <a id="android-internal-fields"></a> JSON output changes for newer Android versions
**Symptom:** The JSON output differs when running on newer Android versions
@ -185,7 +231,7 @@ When no built-in adapter for a type exists and no custom adapter has been regist
If 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`.
## JSON output contains values of `static` fields
## <a id="json-static-fields"></a> JSON output contains values of `static` fields
**Symptom:** The JSON output contains values of `static` fields
@ -193,7 +239,7 @@ If you want to prevent using reflection on third-party classes in the future you
**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.
## `NoSuchMethodError` when calling Gson methods
## <a id="no-such-method-error"></a> `NoSuchMethodError` when calling Gson methods
**Symptom:** A `java.lang.NoSuchMethodError` is thrown when trying to call certain Gson methods
@ -210,3 +256,102 @@ System.out.println(Gson.class.getProtectionDomain().getCodeSource().getLocation(
```
If that fails with a `NullPointerException` you have to try one of the other ways to find out where a class is loaded from.
## <a id="duplicate-fields"></a> `IllegalArgumentException`: 'Class ... declares multiple JSON fields named '...''
**Symptom:** An exception with the message 'Class ... declares multiple JSON fields named '...'' is thrown
**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
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.
## <a id="java-lang-class-unsupported"></a> `UnsupportedOperationException` when serializing or deserializing `java.lang.Class`
**Symptom:** An `UnsupportedOperationException` is thrown when trying to serialize or deserialize `java.lang.Class`
**Reason:** Gson intentionally does not permit serializing and deserializing `java.lang.Class` for security reasons. Otherwise a malicious user could make your application load an arbitrary class from the classpath and, depending on what your application does with the `Class`, in the worst case perform a remote code execution attack.
**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.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).
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:
```
# Keep the no-args constructor of the deserialized class
-keepclassmembers class com.example.MyClass {
<init>();
}
```
You can also use `<init>(...);` 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).
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.
## <a id="typetoken-type-variable"></a> `IllegalArgumentException`: 'TypeToken type argument must not contain a type variable'
**Symptom:** An exception with the message 'TypeToken type argument must not contain a type variable' is thrown
**Reason:** This exception is thrown when you create an anonymous `TypeToken` subclass which captures a type variable, for example `new TypeToken<List<T>>() {}` (where `T` is a type variable). At compile time such code looks safe and you can use the type `List<T>` without any warnings. However, this code is not actually type-safe because at runtime due to [type erasure](https://dev.java/learn/generics/type-erasure/) only the upper bound of the type variable is available. For the previous example that would be `List<Object>`. When using such a `TypeToken` with any Gson methods performing deserialization this would lead to confusing and difficult to debug `ClassCastException`s. For serialization it can in some cases also lead to undesired results.
Note: Earlier version of Gson unfortunately did not prevent capturing type variables, which caused many users to unwittingly write type-unsafe code.
**Solution:**
- 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.
- For Kotlin users: Use [`reified` type parameters](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters), that means change `<T>` to `<reified T>`, if possible. If you have a chain of functions with type parameters you will probably have to make all of them `reified`.
- If you don't actually use Gson's `TypeToken` for any Gson method, use a general purpose 'type token' implementation provided by a different library instead, for example Guava's [`com.google.common.reflect.TypeToken`](https://javadoc.io/doc/com.google.guava/guava/latest/com/google/common/reflect/TypeToken.html).
For backward compatibility it is possible to restore Gson's old behavior of allowing `TypeToken` to capture type variables by setting the [system property](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setProperty(java.lang.String,java.lang.String)) `gson.allowCapturingTypeVariables` to `"true"`, **however**:
- This does not solve any of the type-safety problems mentioned above; in the long term you should prefer one of the other solutions listed above. This system property might be removed in future Gson versions.
- You should only ever set the property to `"true"`, but never to any other value or manually clear it. Otherwise this might counteract any libraries you are using which might have deliberately set the system property because they rely on its behavior.

View File

@ -135,7 +135,7 @@ BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
// ==> {"value1":1,"value2":"abc"}
```
Note that you can not serialize objects with circular references since that will result in infinite recursion.
@ -222,7 +222,7 @@ Gson gson = new Gson();
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
// Serialization
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
String json = gson.toJson(ints); // ==> [1,2,3,4,5]
// Deserialization
TypeToken<Collection<Integer>> collectionType = new TypeToken<Collection<Integer>>(){};
@ -251,14 +251,14 @@ stringMap.put("key", "value");
stringMap.put(null, "null-entry");
// Serialization
String json = gson.toJson(stringMap); // ==> json is {"key":"value","null":"null-entry"}
String json = gson.toJson(stringMap); // ==> {"key":"value","null":"null-entry"}
Map<Integer, Integer> intMap = new LinkedHashMap<>();
intMap.put(2, 4);
intMap.put(3, 6);
// Serialization
String json = gson.toJson(intMap); // ==> json is {"2":4,"3":6}
String json = gson.toJson(intMap); // ==> {"2":4,"3":6}
```
For deserialization Gson uses the `read` method of the `TypeAdapter` registered for the Map key type. Similar to the Collection example shown above, for deserialization a `TypeToken` has to be used to tell Gson what types the Map keys and values have:
@ -297,12 +297,12 @@ complexMap.put(new PersonName("Jane", "Doe"), 35);
// Serialization; complex map is serialized as a JSON array containing key-value pairs (as JSON arrays)
String json = gson.toJson(complexMap);
// ==> json is [[{"firstName":"John","lastName":"Doe"},30],[{"firstName":"Jane","lastName":"Doe"},35]]
// ==> [[{"firstName":"John","lastName":"Doe"},30],[{"firstName":"Jane","lastName":"Doe"},35]]
Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
// Serialization; non-complex map is serialized as a regular JSON object
String json = gson.toJson(stringMap); // json is {"key":"value"}
String json = gson.toJson(stringMap); // ==> {"key":"value"}
```
**Important:** Because Gson by default uses `toString()` to serialize Map keys, this can lead to malformed encoded keys or can cause mismatch between serialization and deserialization of the keys, for example when `toString()` is not properly implemented. A workaround for this can be to use `enableComplexMapKeySerialization()` to make sure the `TypeAdapter` registered for the Map key type is used for deserialization _and_ serialization. As shown in the example above, when none of the keys are serialized by the adapter as JSON array or JSON object, the Map is serialized as a regular JSON object, as desired.
@ -651,7 +651,6 @@ public class SampleObjectForTest {
@Foo private final int annotatedField;
private final String stringField;
private final long longField;
private final Class<?> clazzField;
public SampleObjectForTest() {
annotatedField = 5;

View File

@ -6,7 +6,15 @@ 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 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. 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).

View File

@ -20,7 +20,7 @@
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.10.2-SNAPSHOT</version>
<version>2.10.3-SNAPSHOT</version>
</parent>
<artifactId>gson</artifactId>
@ -34,6 +34,13 @@
</licenses>
<dependencies>
<!--
errorprone was removed due to JPMS:
It complained that the module didn't exist when included in module-info.java,
otherwise it complained about it existing but not being referenced.
In other words, it just didn't work.
-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@ -42,13 +49,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.1.2-jre</version>
<scope>test</scope>
</dependency>
</dependencies>
@ -69,7 +75,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>
@ -79,6 +85,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<!-- Adjust standard `default-compile` execution -->
<execution>
<id>default-compile</id>
<configuration>
@ -88,6 +95,7 @@
</excludes>
</configuration>
</execution>
<!-- Adjust standard `default-testCompile` execution -->
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
@ -125,7 +133,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M9</version>
<configuration>
<!-- Deny illegal access, this is required for ReflectionAccessTest -->
<!-- Requires Java >= 9; Important: In case future Java versions

View File

@ -16,14 +16,22 @@
package com.google.gson;
import com.google.gson.stream.JsonWriter;
import java.util.Objects;
/**
* A class used to control what the serialization looks like.
* 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#setFormattingStyle(FormattingStyle)
* @see JsonWriter#setFormattingStyle(FormattingStyle)
* @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a>
*
* @since $next-version$
@ -31,11 +39,30 @@ import java.util.Objects;
public class FormattingStyle {
private final String newline;
private final String indent;
private final boolean spaceAfterSeparators;
static public final FormattingStyle DEFAULT =
new FormattingStyle("\n", " ");
/**
* The default compact formatting style:
* <ul>
* <li>no newline
* <li>no indent
* <li>no space after {@code ','} and {@code ':'}
* </ul>
*/
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]*")) {
@ -44,17 +71,18 @@ public class FormattingStyle {
}
if (!indent.matches("[ \t]*")) {
throw new IllegalArgumentException(
"Only combinations of spaces and tabs allowed in indent.");
"Only combinations of spaces and tabs are allowed in indent.");
}
this.newline = newline;
this.indent = indent;
this.spaceAfterSeparators = spaceAfterSeparators;
}
/**
* Creates a {@link FormattingStyle} with the specified newline setting.
*
* <p>It can be used to accommodate certain OS convention, for example
* hardcode {@code "\r"} for Linux and macos, {@code "\r\n"} for Windows, or
* hardcode {@code "\n"} for Linux and macOS, {@code "\r\n"} for Windows, or
* call {@link java.lang.System#lineSeparator()} to match the current OS.</p>
*
* <p>Only combinations of {@code \n} and {@code \r} are allowed.</p>
@ -63,7 +91,7 @@ public class FormattingStyle {
* @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);
}
/**
@ -75,11 +103,26 @@ public class FormattingStyle {
* @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);
}
/**
* The string value that will be used as a newline.
* 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);
}
/**
* Returns the string value that will be used as a newline.
*
* @return the newline value.
*/
@ -88,11 +131,18 @@ public class FormattingStyle {
}
/**
* 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;
}
}

View File

@ -16,7 +16,14 @@
package com.google.gson;
import com.google.gson.internal.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.DefaultConfig;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.GsonBuildConfig;
import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.internal.Primitives;
import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.ArrayTypeAdapter;
import com.google.gson.internal.bind.CollectionTypeAdapterFactory;
import com.google.gson.internal.bind.DateTypeAdapter;
@ -97,13 +104,17 @@ import java.util.concurrent.atomic.AtomicLongArray;
* 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>Lenient JSON handling</h2>
* <h2 id="default-lenient">JSON Strictness handling</h2>
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not
* comply with the JSON specification, regardless of whether {@link GsonBuilder#setLenient()}
* is used or not. If this behavior is not desired, the following workarounds can be used:
* comply with the JSON specification when no explicit {@linkplain Strictness strictness} is set (the default).
* To specify the strictness of a {@code Gson} instance, you should set it through
* {@link GsonBuilder#setStrictness(Strictness)}.
*
* <p>For older Gson versions, which don't have the strictness mode API, the following
* workarounds can be used:
*
* <h3>Serialization</h3>
* <ol>
@ -127,6 +138,10 @@ import java.util.concurrent.atomic.AtomicLongArray;
* to make sure there is no trailing data
* </ol>
*
* Note that the {@code JsonReader} created this way is only 'legacy strict', it mostly adheres
* to the JSON specification but allows small deviations. See {@link JsonReader#setStrictness(Strictness)}
* for details.
*
* @see TypeToken
*
* @author Inderjeet Singh
@ -165,7 +180,7 @@ public final class Gson {
final boolean generateNonExecutableJson;
final boolean htmlSafe;
final FormattingStyle formattingStyle;
final boolean lenient;
final Strictness strictness;
final boolean omitQuotes;
final boolean serializeSpecialFloatingPointValues;
final boolean useJdkUnsafe;
@ -187,7 +202,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
@ -213,13 +228,15 @@ public final class Gson {
* <li>By default, Gson excludes <code>transient</code> or <code>static</code> fields from
* consideration for serialization and deserialization. You can change this behavior through
* {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
* <li>No explicit strictness is set. You can change this by calling
* {@link GsonBuilder#setStrictness(Strictness)}.</li>
* </ul>
*/
public Gson() {
this(Excluder.DEFAULT, DefaultConfig.DEFAULT_FIELD_NAMING_STRATEGY,
Collections.<Type, InstanceCreator<?>>emptyMap(), DefaultConfig.DEFAULT_SERIALIZE_NULLS,
DefaultConfig.DEFAULT_COMPLEX_MAP_KEYS, DefaultConfig.DEFAULT_DUPLICATE_MAP_KEYS, DefaultConfig.DEFAULT_JSON_NON_EXECUTABLE, DefaultConfig.DEFAULT_ESCAPE_HTML,
DefaultConfig.DEFAULT_FORMATTING_STYLE, true, DefaultConfig.DEFAULT_OMIT_QUOTES, DefaultConfig.DEFAULT_SPECIALIZE_FLOAT_VALUES,
DefaultConfig.DEFAULT_FORMATTING_STYLE, Strictness.LENIENT, DefaultConfig.DEFAULT_OMIT_QUOTES, DefaultConfig.DEFAULT_SPECIALIZE_FLOAT_VALUES,
DefaultConfig.DEFAULT_USE_JDK_UNSAFE,
LongSerializationPolicy.DEFAULT, DefaultConfig.DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
@ -230,7 +247,7 @@ public final class Gson {
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean duplicateMapKeyDeserialization, boolean generateNonExecutableGson, boolean htmlSafe,
FormattingStyle formattingStyle, boolean lenient, boolean omitQuotes, boolean serializeSpecialFloatingPointValues,
FormattingStyle formattingStyle, Strictness strictness, boolean omitQuotes, boolean serializeSpecialFloatingPointValues,
boolean useJdkUnsafe,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
@ -248,7 +265,7 @@ public final class Gson {
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.formattingStyle = formattingStyle;
this.lenient = lenient;
this.strictness = strictness;
this.omitQuotes = omitQuotes;
this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
this.useJdkUnsafe = useJdkUnsafe;
@ -568,42 +585,50 @@ public final class Gson {
* adapter that does a little bit of work but then delegates further processing to the Gson
* default type adapter. Here is an example:
* <p>Let's say we want to write a type adapter that counts the number of objects being read
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
* the <code>getDelegateAdapter</code> method:
* <pre> {@code
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
* public int numReads = 0;
* public int numWrites = 0;
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
* final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
* return new TypeAdapter<T>() {
* public void write(JsonWriter out, T value) throws IOException {
* ++numWrites;
* delegate.write(out, value);
* }
* public T read(JsonReader in) throws IOException {
* ++numReads;
* return delegate.read(in);
* }
* };
* }
* }
* } </pre>
* This factory can now be used like this:
* <pre> {@code
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
* // Call gson.toJson() and fromJson methods on objects
* System.out.println("Num JSON reads" + stats.numReads);
* System.out.println("Num JSON writes" + stats.numWrites);
* }</pre>
* Note that this call will skip all factories registered before {@code skipPast}. In case of
* multiple TypeAdapterFactories registered it is up to the caller of this function to insure
* that the order of registration does not prevent this method from reaching a factory they
* would expect to reply from this call.
* Note that since you can not override type adapter factories for String and Java primitive
* types, our stats factory will not count the number of String or primitives that will be
* read or written.
* from or written to JSON. We can achieve this by writing a type adapter factory that uses
* the <code>getDelegateAdapter</code> method:
* <pre>{@code
* class StatsTypeAdapterFactory implements TypeAdapterFactory {
* public int numReads = 0;
* public int numWrites = 0;
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
* final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
* return new TypeAdapter<T>() {
* public void write(JsonWriter out, T value) throws IOException {
* ++numWrites;
* delegate.write(out, value);
* }
* public T read(JsonReader in) throws IOException {
* ++numReads;
* return delegate.read(in);
* }
* };
* }
* }
* }</pre>
* This factory can now be used like this:
* <pre>{@code
* StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
* Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
* // Call gson.toJson() and fromJson methods on objects
* System.out.println("Num JSON reads: " + stats.numReads);
* System.out.println("Num JSON writes: " + stats.numWrites);
* }</pre>
* Note that this call will skip all factories registered before {@code skipPast}. In case of
* multiple TypeAdapterFactories registered it is up to the caller of this function to insure
* that the order of registration does not prevent this method from reaching a factory they
* would expect to reply from this call.
* Note that since you can not override the type adapter factories for some types, see
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}, our stats factory will not count
* the number of instances of those types that will be read or written.
*
* <p>If {@code skipPast} is a factory which has neither been registered on the {@link GsonBuilder}
* nor specified with the {@link JsonAdapter @JsonAdapter} annotation on a class, then this
* method behaves as if {@link #getAdapter(TypeToken)} had been called. This also means that
* for fields with {@code @JsonAdapter} annotation this method behaves normally like {@code getAdapter}
* (except for corner cases where a custom {@link InstanceCreator} is used to create an
* instance of the factory).
*
* @param skipPast The type adapter factory that needs to be skipped while searching for
* a matching type adapter. In most cases, you should just pass <i>this</i> (the type adapter
* factory from where {@code getDelegateAdapter} method is being invoked).
@ -612,9 +637,10 @@ public final class Gson {
* @since 2.2
*/
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
// Hack. If the skipPast factory isn't registered, assume the factory is being requested via
// our @JsonAdapter annotation.
if (!factories.contains(skipPast)) {
Objects.requireNonNull(skipPast, "skipPast must not be null");
Objects.requireNonNull(type, "type must not be null");
if (jsonAdapterFactory.isClassJsonAdapterFactory(type, skipPast)) {
skipPast = jsonAdapterFactory;
}
@ -632,7 +658,13 @@ public final class Gson {
return candidate;
}
}
throw new IllegalArgumentException("GSON cannot serialize " + type);
if (skipPastFound) {
throw new IllegalArgumentException("GSON cannot serialize " + type);
} else {
// Probably a factory from @JsonAdapter on a field
return getAdapter(type);
}
}
/**
@ -777,7 +809,7 @@ public final class Gson {
* <pre>
* Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
* </pre>
* @param writer Writer to which the JSON representation of src needs to be written.
* @param writer Writer to which the JSON representation of src needs to be written
* @throws JsonIOException if there was a problem writing to the writer
* @since 1.2
*
@ -797,26 +829,40 @@ public final class Gson {
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the writer will be restored when this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @param src the object for which JSON representation is to be created
* @param typeOfSrc the type of the object to be written
* @param writer Writer to which the JSON representation of src needs to be written
*
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
@SuppressWarnings("unchecked")
TypeAdapter<Object> adapter = (TypeAdapter<Object>) getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
writer.setLenient(lenient);
Strictness oldStrictness = writer.getStrictness();
if (this.strictness != null) {
writer.setStrictness(this.strictness);
} else if (writer.getStrictness() != Strictness.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}
boolean oldOmitQuotes = writer.getOmitQuotes();
writer.setOmitQuotes(omitQuotes);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setOmitQuotes(omitQuotes);
writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls);
try {
adapter.write(writer, src);
@ -825,7 +871,7 @@ public final class Gson {
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
writer.setLenient(oldLenient);
writer.setStrictness(oldStrictness);
writer.setOmitQuotes(oldOmitQuotes);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
@ -870,10 +916,13 @@ public final class Gson {
* <li>{@link GsonBuilder#disableHtmlEscaping()}</li>
* <li>{@link GsonBuilder#generateNonExecutableJson()}</li>
* <li>{@link GsonBuilder#serializeNulls()}</li>
* <li>{@link GsonBuilder#setLenient()}</li>
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If no
* {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness has been set} the created
* writer will have a strictness of {@link Strictness#LEGACY_STRICT}. Otherwise, the strictness of
* the {@code Gson} instance will be used for the created writer.</li>
* <li>{@link GsonBuilder#setOmitQuotes()}</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 {
@ -881,52 +930,75 @@ public final class Gson {
writer.write(DefaultConfig.JSON_NON_EXECUTABLE_PREFIX);
}
JsonWriter jsonWriter = new JsonWriter(writer);
jsonWriter.setFormattingStyle(formattingStyle);
jsonWriter.setHtmlSafe(htmlSafe);
jsonWriter.setLenient(lenient);
jsonWriter.setOmitQuotes(omitQuotes);
jsonWriter.setSerializeNulls(serializeNulls);
jsonWriter.setSerializeSpecialFloatingPointValues(serializeSpecialFloatingPointValues);
configure(jsonWriter);
return jsonWriter;
}
public void configure(JsonWriter writer) {
writer.setFormattingStyle(formattingStyle);
writer.setHtmlSafe(htmlSafe);
writer.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
writer.setOmitQuotes(omitQuotes);
writer.setSerializeNulls(serializeNulls);
writer.setSerializeSpecialFloatingPointValues(serializeSpecialFloatingPointValues);
}
/**
* Returns a new JSON reader configured for the settings on this Gson instance.
*
* <p>The following settings are considered:
* <ul>
* <li>{@link GsonBuilder#setLenient()}</li>
* <li>{@link GsonBuilder#setStrictness(Strictness)}. If no
* {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness has been set} the created
* reader will have a strictness of {@link Strictness#LEGACY_STRICT}. Otherwise, the strictness of
* the {@code Gson} instance will be used for the created reader.</li>
* </ul>
*/
public JsonReader newJsonReader(Reader reader) {
JsonReader jsonReader = new JsonReader(reader);
jsonReader.setLenient(lenient);
jsonReader.setSerializeSpecialFloatingPointValues(serializeSpecialFloatingPointValues);
configure(jsonReader);
return jsonReader;
}
public void configure(JsonReader reader) {
reader.setStrictness(strictness == null ? Strictness.LEGACY_STRICT : strictness);
reader.setSerializeSpecialFloatingPointValues(serializeSpecialFloatingPointValues);
}
/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for writing the JSON regardless of the {@linkplain JsonWriter#getStrictness() strictness}
* of the provided {@link JsonWriter}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the writer does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the writer will be restored when this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @param jsonElement the JSON element to be written
* @param writer the JSON writer to which the provided element will be written
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
boolean oldLenient = writer.isLenient();
writer.setLenient(lenient);
Strictness oldStrictness = writer.getStrictness();
boolean oldOmitQuotes = writer.getOmitQuotes();
writer.setOmitQuotes(omitQuotes);
boolean oldHtmlSafe = writer.isHtmlSafe();
writer.setHtmlSafe(htmlSafe);
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setOmitQuotes(omitQuotes);
writer.setHtmlSafe(htmlSafe);
writer.setSerializeNulls(serializeNulls);
if (this.strictness != null) {
writer.setStrictness(this.strictness);
} else if (writer.getStrictness() != Strictness.STRICT) {
writer.setStrictness(Strictness.LENIENT);
}
try {
Streams.write(jsonElement, writer);
} catch (IOException e) {
@ -934,7 +1006,7 @@ public final class Gson {
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
writer.setLenient(oldLenient);
writer.setStrictness(oldStrictness);
writer.setOmitQuotes(oldOmitQuotes);
writer.setHtmlSafe(oldHtmlSafe);
writer.setSerializeNulls(oldSerializeNulls);
@ -996,7 +1068,7 @@ public final class Gson {
* @see #fromJson(String, Class)
* @see #fromJson(String, TypeToken)
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
return (T) fromJson(json, TypeToken.get(typeOfT));
}
@ -1089,7 +1161,7 @@ public final class Gson {
* @see #fromJson(Reader, Class)
* @see #fromJson(Reader, TypeToken)
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
return (T) fromJson(json, TypeToken.get(typeOfT));
}
@ -1154,9 +1226,12 @@ public final class Gson {
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
*
* @param <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
@ -1168,7 +1243,7 @@ public final class Gson {
* @see #fromJson(Reader, Type)
* @see #fromJson(JsonReader, TypeToken)
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
return (T) fromJson(reader, TypeToken.get(typeOfT));
}
@ -1183,9 +1258,12 @@ public final class Gson {
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
* <p>If the {@code Gson} instance has an {@linkplain GsonBuilder#setStrictness(Strictness) explicit strictness setting},
* this setting will be used for reading the JSON regardless of the {@linkplain JsonReader#getStrictness() strictness}
* of the provided {@link JsonReader}. For legacy reasons, if the {@code Gson} instance has no explicit strictness setting
* and the reader does not have the strictness {@link Strictness#STRICT}, the JSON will be written in {@link Strictness#LENIENT}
* mode.<br>
* Note that in all cases the old strictness setting of the reader will be restored when this method returns.
*
* @param <T> the type of the desired object
* @param reader the reader whose next JSON value should be deserialized
@ -1205,10 +1283,16 @@ public final class Gson {
*/
public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(lenient);
Strictness oldStrictness = reader.getStrictness();
if (this.strictness != null) {
reader.setStrictness(this.strictness);
} else if (reader.getStrictness() != Strictness.STRICT) {
reader.setStrictness(Strictness.LENIENT);
}
try {
reader.peek();
JsonToken unused = reader.peek();
isEmpty = false;
TypeAdapter<T> typeAdapter = getAdapter(typeOfT);
return typeAdapter.read(reader);
@ -1229,7 +1313,7 @@ public final class Gson {
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
reader.setLenient(oldLenient);
reader.setStrictness(oldStrictness);
}
}
@ -1282,7 +1366,7 @@ public final class Gson {
* @see #fromJson(JsonElement, Class)
* @see #fromJson(JsonElement, TypeToken)
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
return (T) fromJson(json, TypeToken.get(typeOfT));
}

View File

@ -46,8 +46,7 @@ import java.util.Objects;
* use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its
* various configuration methods, and finally calling create.</p>
*
* <p>The following is an example shows how to use the {@code GsonBuilder} to construct a Gson
* instance:
* <p>The following example shows how to use the {@code GsonBuilder} to construct a Gson instance:
*
* <pre>
* Gson gson = new GsonBuilder()
@ -61,12 +60,16 @@ import java.util.Objects;
* .create();
* </pre>
*
* <p>NOTES:
* <p>Notes:
* <ul>
* <li> the order of invocation of configuration methods does not matter.</li>
* <li> The default serialization of {@link Date} and its subclasses in Gson does
* <li>The order of invocation of configuration methods does not matter.</li>
* <li>The default serialization of {@link Date} and its subclasses in Gson does
* not contain time-zone information. So, if you are using date/time instances,
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods
* behave as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as
* if {@link Strictness#LENIENT} was used. Prefer explicitly setting a strictness
* with {@link #setStrictness(Strictness)} to avoid this legacy behavior.
* </ul>
*
* @author Inderjeet Singh
@ -91,7 +94,7 @@ public final class GsonBuilder {
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
private FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE;
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
private boolean lenient = DEFAULT_LENIENT;
private Strictness strictness = DEFAULT_STRICTNESS;
private boolean omitQuotes = DEFAULT_OMIT_QUOTES;
private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
@ -111,7 +114,7 @@ public final class 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;
@ -123,7 +126,7 @@ public final class GsonBuilder {
this.generateNonExecutableJson = gson.generateNonExecutableJson;
this.escapeHtmlChars = gson.htmlSafe;
this.formattingStyle = gson.formattingStyle;
this.lenient = gson.lenient;
this.strictness = gson.strictness;
this.omitQuotes = gson.omitQuotes;
this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues;
this.longSerializationPolicy = gson.longSerializationPolicy;
@ -262,7 +265,7 @@ public final class GsonBuilder {
* {"x":2,"y":3}}.
*
* <p>Given the assumption above, a {@code Map<Point, String>} 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).
*
* <p>Below is an example of serializing complex types as JSON arrays:
* <pre> {@code
@ -491,38 +494,60 @@ public final class GsonBuilder {
* 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 #setFormattingStyle(FormattingStyle)}
* with {@link FormattingStyle#PRETTY}.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder setPrettyPrinting() {
return setPrettyPrinting(FormattingStyle.DEFAULT);
return setFormattingStyle(FormattingStyle.PRETTY);
}
/**
* Configures Gson to output JSON that uses a certain kind of formatting stile (for example newline and indent).
* This option only affects JSON serialization.
*
* <p>Has no effect if the serialized format is a single line.</p>
* 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.
*
* @param formattingStyle the formatting style to use.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since $next-version$
*/
public GsonBuilder setPrettyPrinting(FormattingStyle formattingStyle) {
this.formattingStyle = formattingStyle;
public GsonBuilder setFormattingStyle(FormattingStyle formattingStyle) {
this.formattingStyle = Objects.requireNonNull(formattingStyle);
return this;
}
/**
* Configures Gson to allow JSON data which does not strictly comply with the JSON specification.
* Sets the strictness of this builder to {@link Strictness#LENIENT}.
*
* <p>Note: Due to legacy reasons most methods of Gson are always lenient, regardless of
* whether this builder method is used.
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with
* {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @see JsonReader#setLenient(boolean)
* @see JsonWriter#setLenient(boolean)
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @see #setStrictness(Strictness)
*/
@Deprecated
public GsonBuilder setLenient() {
lenient = true;
return setStrictness(Strictness.LENIENT);
}
/**
* Sets the strictness of this builder to the provided parameter.
*
* <p>This changes how strict the
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a> is enforced when parsing or
* writing JSON. For details on this, refer to {@link JsonReader#setStrictness(Strictness)} and
* {@link JsonWriter#setStrictness(Strictness)}.</p>
*
* @param strictness the new strictness mode. May not be {@code null}.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
* @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @since $next-version$
*/
public GsonBuilder setStrictness(Strictness strictness) {
this.strictness = Objects.requireNonNull(strictness);
return this;
}
@ -570,7 +595,7 @@ public final class GsonBuilder {
}
/**
* 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.
*
@ -590,7 +615,7 @@ public final class GsonBuilder {
}
/**
* 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.
*
@ -703,7 +728,7 @@ public final class GsonBuilder {
}
/**
* Section 2.4 of <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a> disallows
* Section 6 of <a href="https://www.ietf.org/rfc/rfc8259.txt">JSON specification</a> disallows
* special double values (NaN, Infinity, -Infinity). However,
* <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
* specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
@ -793,7 +818,7 @@ public final class GsonBuilder {
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
serializeNulls, complexMapKeySerialization, duplicateMapKeyDeserialization,
generateNonExecutableJson, escapeHtmlChars, formattingStyle, lenient, omitQuotes,
generateNonExecutableJson, escapeHtmlChars, formattingStyle, strictness, omitQuotes,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
new ArrayList<>(this.hierarchyFactories), factories,

View File

@ -63,7 +63,7 @@ import java.lang.reflect.Type;
* </pre>
*
* <p>Note that it does not matter what the fields of the created instance contain since Gson will
* overwrite them with the deserialized values specified in Json. You should also ensure that a
* overwrite them with the deserialized values specified in JSON. You should also ensure that a
* <i>new</i> object is returned, not a common object since its fields will be overwritten.
* The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p>
*
@ -73,6 +73,8 @@ import java.lang.reflect.Type;
*
* @param <T> the type of object that will be created by this implementation.
*
* @see GsonBuilder#registerTypeAdapter(Type, Object)
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
@ -81,7 +83,7 @@ public interface InstanceCreator<T> {
/**
* Gson invokes this call-back method during deserialization to create an instance of the
* specified type. The fields of the returned instance are overwritten with the data present
* in the Json. Since the prior contents of the object are destroyed and overwritten, do not
* in the JSON. Since the prior contents of the object are destroyed and overwritten, do not
* return an instance that is useful elsewhere. In particular, do not return a common instance,
* always use {@code new} to create a new instance.
*

View File

@ -40,5 +40,6 @@ public interface JsonDeserializationContext {
* @return An object of type typeOfT.
* @throws JsonParseException if the parse tree does not contain expected data.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
}

View File

@ -319,7 +319,8 @@ public abstract class JsonElement {
try {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive contains NaN
jsonWriter.setStrictness(Strictness.LENIENT);
Streams.write(this, jsonWriter);
return stringWriter.toString();
} catch (IOException e) {

View File

@ -40,7 +40,7 @@ public final class JsonParser {
* An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* <p>The JSON string is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
* <p>The JSON string is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -56,7 +56,7 @@ public final class JsonParser {
* An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
*
* @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -86,8 +86,8 @@ public final class JsonParser {
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
* regardless of the strictness setting of the provided reader. The strictness setting
* of the reader is restored once this method returns.
*
* @throws JsonParseException if there is an IOException or if the specified
@ -96,8 +96,8 @@ public final class JsonParser {
*/
public static JsonElement parseReader(JsonReader reader)
throws JsonIOException, JsonSyntaxException {
boolean lenient = reader.isLenient();
reader.setLenient(true);
Strictness strictness = reader.getStrictness();
reader.setStrictness(Strictness.LENIENT);
try {
return Streams.parse(reader);
} catch (StackOverflowError e) {
@ -105,7 +105,7 @@ public final class JsonParser {
} catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
} finally {
reader.setLenient(lenient);
reader.setStrictness(strictness);
}
}

View File

@ -28,12 +28,12 @@ import java.util.NoSuchElementException;
/**
* A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
* asynchronously. The JSON data is parsed in lenient mode, see also
* {@link JsonReader#setLenient(boolean)}.
* {@link JsonReader#setStrictness(Strictness)}.
*
* <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To
* properly use this class across multiple threads, you will need to add some external
* synchronization. For example:
*
*
* <pre>
* JsonStreamParser parser = new JsonStreamParser("['first'] {'second':10} 'third'");
* JsonElement element;
@ -66,7 +66,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
*/
public JsonStreamParser(Reader reader) {
parser = new JsonReader(reader);
parser.setLenient(true);
parser.setStrictness(Strictness.LENIENT);
lock = new Object();
}

View File

@ -0,0 +1,29 @@
package com.google.gson;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or
* {@linkplain JsonWriter writer} follows the syntax laid out in the
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a>.
*
* <p>You can look at {@link JsonReader#setStrictness(Strictness)} to see how the strictness
* affects the {@link JsonReader} and you can look at
* {@link JsonWriter#setStrictness(Strictness)} to see how the strictness
* affects the {@link JsonWriter}.</p>
*
* @see JsonReader#setStrictness(Strictness)
* @see JsonWriter#setStrictness(Strictness)
* @since $next-version$
*/
public enum Strictness {
/** Allow large deviations from the JSON specification. */
LENIENT,
/** Allow certain small deviations from the JSON specification for legacy reasons. */
LEGACY_STRICT,
/** Strict compliance with the JSON specification. */
STRICT
}

View File

@ -34,8 +34,7 @@ import java.io.Writer;
* By default Gson converts application classes to JSON using its built-in type
* adapters. If Gson's default JSON conversion isn't appropriate for a type,
* extend this class to customize the conversion. Here's an example of a type
* adapter for an (X,Y) coordinate point: <pre> {@code
*
* adapter for an (X,Y) coordinate point: <pre>{@code
* public class PointAdapter extends TypeAdapter<Point> {
* public Point read(JsonReader reader) throws IOException {
* if (reader.peek() == JsonToken.NULL) {
@ -85,8 +84,7 @@ import java.io.Writer;
* guarantees of {@link Gson} might not apply.
*
* <p>To use a custom type adapter with Gson, you must <i>register</i> it with a
* {@link GsonBuilder}: <pre> {@code
*
* {@link GsonBuilder}: <pre>{@code
* GsonBuilder builder = new GsonBuilder();
* builder.registerTypeAdapter(Point.class, new PointAdapter());
* // if PointAdapter didn't check for nulls in its read/write methods, you should instead use
@ -102,14 +100,12 @@ import java.io.Writer;
// <h2>JSON Conversion</h2>
// <p>A type adapter registered with Gson is automatically invoked while serializing
// or deserializing JSON. However, you can also use type adapters directly to serialize
// and deserialize JSON. Here is an example for deserialization: <pre> {@code
//
// and deserialize JSON. Here is an example for deserialization: <pre>{@code
// String json = "{'origin':'0,0','points':['1,2','3,4']}";
// TypeAdapter<Graph> graphAdapter = gson.getAdapter(Graph.class);
// Graph graph = graphAdapter.fromJson(json);
// }</pre>
// And an example for serialization: <pre> {@code
//
// And an example for serialization: <pre>{@code
// Graph graph = new Graph(...);
// TypeAdapter<Graph> graphAdapter = gson.getAdapter(Graph.class);
// String json = graphAdapter.toJson(graph);
@ -134,12 +130,12 @@ public abstract class TypeAdapter<T> {
/**
* Converts {@code value} to a JSON document and writes it to {@code out}.
* Unlike Gson's similar {@link Gson#toJson(JsonElement, Appendable) toJson}
* method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(JsonWriter, Object)} for lenient writing.
*
* @param value the Java object to convert. May be null.
* <p>A {@link JsonWriter} with default configuration is used for writing the
* JSON data. To customize this behavior, create a {@link JsonWriter}, configure
* it and then use {@link #write(JsonWriter, Object)} instead.
*
* @param value the Java object to convert. May be {@code null}.
* @since 2.2
*/
public final void toJson(Writer out, T value) throws IOException {
@ -151,8 +147,7 @@ public abstract class TypeAdapter<T> {
* This wrapper method is used to make a type adapter null tolerant. In general, a
* type adapter is required to handle nulls in write and read methods. Here is how this
* is typically done:<br>
* <pre> {@code
*
* <pre>{@code
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
* new TypeAdapter<Foo>() {
* public Foo read(JsonReader in) throws IOException {
@ -173,8 +168,7 @@ public abstract class TypeAdapter<T> {
* }</pre>
* You can avoid this boilerplate handling of nulls by wrapping your type adapter with
* this method. Here is how we will rewrite the above example:
* <pre> {@code
*
* <pre>{@code
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
* new TypeAdapter<Foo>() {
* public Foo read(JsonReader in) throws IOException {
@ -207,13 +201,14 @@ public abstract class TypeAdapter<T> {
}
/**
* Converts {@code value} to a JSON document. Unlike Gson's similar {@link
* Gson#toJson(Object) toJson} method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(JsonWriter, Object)} for lenient writing.
* Converts {@code value} to a JSON document.
*
* <p>A {@link JsonWriter} with default configuration is used for writing the
* JSON data. To customize this behavior, create a {@link JsonWriter}, configure
* it and then use {@link #write(JsonWriter, Object)} instead.
*
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
* @param value the Java object to convert. May be null.
* @param value the Java object to convert. May be {@code null}.
* @since 2.2
*/
public final String toJson(T value) {
@ -229,7 +224,7 @@ public abstract class TypeAdapter<T> {
/**
* Converts {@code value} to a JSON tree.
*
* @param value the Java object to convert. May be null.
* @param value the Java object to convert. May be {@code null}.
* @return the converted JSON tree. May be {@link JsonNull}.
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
* @since 2.2
@ -248,20 +243,22 @@ public abstract class TypeAdapter<T> {
* Reads one JSON value (an array, object, string, number, boolean or null)
* and converts it to a Java object. Returns the converted object.
*
* @return the converted Java object. May be null.
* @return the converted Java object. May be {@code null}.
*/
public abstract T read(JsonReader in) throws IOException;
/**
* Converts the JSON document in {@code in} to a Java object. Unlike Gson's
* similar {@link Gson#fromJson(Reader, Class) fromJson} method, this
* read is strict. Create a {@link JsonReader#setLenient(boolean) lenient}
* {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading.
* Converts the JSON document in {@code in} to a Java object.
*
* <p>A {@link JsonReader} with default configuration (that is with
* {@link Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data.
* To customize this behavior, create a {@link JsonReader}, configure it and then
* use {@link #read(JsonReader)} instead.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @return the converted Java object. May be null.
* @return the converted Java object. May be {@code null}.
* @since 2.2
*/
public final T fromJson(Reader in) throws IOException {
@ -270,15 +267,17 @@ public abstract class TypeAdapter<T> {
}
/**
* Converts the JSON document in {@code json} to a Java object. Unlike Gson's
* similar {@link Gson#fromJson(String, Class) fromJson} method, this read is
* strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code
* JsonReader} and call {@link #read(JsonReader)} for lenient reading.
* Converts the JSON document in {@code json} to a Java object.
*
* <p>A {@link JsonReader} with default configuration (that is with
* {@link Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data.
* To customize this behavior, create a {@link JsonReader}, configure it and then
* use {@link #read(JsonReader)} instead.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @return the converted Java object. May be null.
* @return the converted Java object. May be {@code null}.
* @since 2.2
*/
public final T fromJson(String json) throws IOException {
@ -289,7 +288,7 @@ public abstract class TypeAdapter<T> {
* Converts {@code jsonTree} to a Java object.
*
* @param jsonTree the JSON element to convert. May be {@link JsonNull}.
* @return the converted Java object. May be null.
* @return the converted Java object. May be {@code null}.
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #read(JsonReader)}
* @since 2.2
*/

View File

@ -17,6 +17,8 @@
package com.google.gson.annotations;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
@ -35,11 +37,13 @@ import java.lang.annotation.Target;
* &#64;JsonAdapter(UserJsonAdapter.class)
* public class User {
* public final String firstName, lastName;
*
* private User(String firstName, String lastName) {
* this.firstName = firstName;
* this.lastName = lastName;
* }
* }
*
* public class UserJsonAdapter extends TypeAdapter&lt;User&gt; {
* &#64;Override public void write(JsonWriter out, User user) throws IOException {
* // implement write: combine firstName and lastName into name
@ -47,8 +51,8 @@ import java.lang.annotation.Target;
* out.name("name");
* out.value(user.firstName + " " + user.lastName);
* out.endObject();
* // implement the write method
* }
*
* &#64;Override public User read(JsonReader in) throws IOException {
* // implement read: split name into firstName and lastName
* in.beginObject();
@ -60,14 +64,15 @@ import java.lang.annotation.Target;
* }
* </pre>
*
* Since User class specified UserJsonAdapter.class in &#64;JsonAdapter annotation, it
* will automatically be invoked to serialize/deserialize User instances.
* Since {@code User} class specified {@code UserJsonAdapter.class} in {@code @JsonAdapter}
* annotation, it will automatically be invoked to serialize/deserialize {@code User} instances.
*
* <p> Here is an example of how to apply this annotation to a field.
* <p>Here is an example of how to apply this annotation to a field.
* <pre>
* private static final class Gadget {
* &#64;JsonAdapter(UserJsonAdapter2.class)
* &#64;JsonAdapter(UserJsonAdapter.class)
* final User user;
*
* Gadget(User user) {
* this.user = user;
* }
@ -75,15 +80,30 @@ import java.lang.annotation.Target;
* </pre>
*
* It's possible to specify different type adapters on a field, that
* field's type, and in the {@link com.google.gson.GsonBuilder}. Field
* annotations take precedence over {@code GsonBuilder}-registered type
* field's type, and in the {@link GsonBuilder}. Field annotations
* take precedence over {@code GsonBuilder}-registered type
* adapters, which in turn take precedence over annotated types.
*
* <p>The class referenced by this annotation must be either a {@link
* TypeAdapter} or a {@link TypeAdapterFactory}, or must implement one
* or both of {@link JsonDeserializer} or {@link JsonSerializer}.
* Using {@link TypeAdapterFactory} makes it possible to delegate
* to the enclosing {@link Gson} instance.
* to the enclosing {@link Gson} instance. By default the specified
* adapter will not be called for {@code null} values; set {@link #nullSafe()}
* to {@code false} to let the adapter handle {@code null} values itself.
*
* <p>The type adapter is created in the same way Gson creates instances of
* custom classes during deserialization, that means:
* <ol>
* <li>If a custom {@link InstanceCreator} has been registered for the
* adapter class, it will be used to create the instance
* <li>Otherwise, if the adapter class has a no-args constructor
* (regardless of which visibility), it will be invoked to create
* the instance
* <li>Otherwise, JDK {@code Unsafe} will be used to create the instance;
* see {@link GsonBuilder#disableJdkUnsafe()} for the unexpected
* side-effects this might have
* </ol>
*
* <p>{@code Gson} instances might cache the adapter they create for
* a {@code @JsonAdapter} annotation. It is not guaranteed that a new
@ -104,7 +124,13 @@ public @interface JsonAdapter {
/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */
Class<?> value();
/** false, to be able to handle {@code null} values within the adapter, default value is true. */
/**
* Whether the adapter referenced by {@link #value()} should be made {@linkplain TypeAdapter#nullSafe() null-safe}.
*
* <p>If {@code true} (the default), it will be made null-safe and Gson will handle {@code null} Java objects
* on serialization and JSON {@code null} on deserialization without calling the adapter. If {@code false},
* the adapter will have to handle the {@code null} values.
*/
boolean nullSafe() default true;
}

View File

@ -33,8 +33,8 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Objects;
import java.util.Properties;
/**
* Static methods for working with types.
@ -138,9 +138,8 @@ public final class $Gson$Types {
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
// I'm not exactly sure why getRawType() returns Type instead of Class.
// Neal isn't either but suspects some pathological case related
// to nested classes exists.
// getRawType() returns Type instead of Class; that seems to be an API mistake,
// see https://bugs.openjdk.org/browse/JDK-8250659
Type rawType = parameterizedType.getRawType();
checkArgument(rawType instanceof Class);
return (Class<?>) rawType;
@ -481,19 +480,33 @@ public final class $Gson$Types {
checkArgument(!(type instanceof Class<?>) || !((Class<?>) type).isPrimitive());
}
/**
* Whether an {@linkplain ParameterizedType#getOwnerType() owner type} must be specified when
* constructing a {@link ParameterizedType} for {@code rawType}.
*
* <p>Note that this method might not require an owner type for all cases where Java reflection
* would create parameterized types with owner type.
*/
public static boolean requiresOwnerType(Type rawType) {
if (rawType instanceof Class<?>) {
Class<?> rawTypeAsClass = (Class<?>) rawType;
return !Modifier.isStatic(rawTypeAsClass.getModifiers())
&& rawTypeAsClass.getDeclaringClass() != null;
}
return false;
}
private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
private final Type ownerType;
private final Type rawType;
private final Type[] typeArguments;
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
// TODO: Should this enforce that rawType is a Class? See JDK implementation of
// the ParameterizedType interface and https://bugs.openjdk.org/browse/JDK-8250659
requireNonNull(rawType);
// require an owner type if the raw type needs it
if (rawType instanceof Class<?>) {
Class<?> rawTypeAsClass = (Class<?>) rawType;
boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers())
|| rawTypeAsClass.getEnclosingClass() == null;
checkArgument(ownerType != null || isStaticOrTopLevelClass);
if (ownerType == null && requiresOwnerType(rawType)) {
throw new IllegalArgumentException("Must specify owner type for " + rawType);
}
this.ownerType = ownerType == null ? null : canonicalize(ownerType);

View File

@ -70,12 +70,21 @@ public final class ConstructorConstructor {
static String checkInstantiable(Class<?> c) {
int modifiers = c.getModifiers();
if (Modifier.isInterface(modifiers)) {
return "Interfaces can't be instantiated! Register an InstanceCreator "
+ "or a TypeAdapter for this type. Interface name: " + c.getName();
return "Interfaces can't be instantiated! Register an InstanceCreator"
+ " or a TypeAdapter for this type. Interface name: " + c.getName();
}
if (Modifier.isAbstract(modifiers)) {
return "Abstract classes can't be instantiated! Register an InstanceCreator "
+ "or a TypeAdapter for this type. Class name: " + c.getName();
// R8 performs aggressive optimizations where it removes the default constructor of a class
// and makes the class `abstract`; check for that here explicitly
/*
* 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;
}
@ -144,9 +153,9 @@ public final class ConstructorConstructor {
// finally try unsafe
return newUnsafeAllocator(rawType);
} else {
final String message = "Unable to create instance of " + rawType + "; ReflectionAccessFilter "
+ "does not permit using reflection or Unsafe. Register an InstanceCreator or a TypeAdapter "
+ "for this type or adjust the access filter to allow using reflection.";
final String message = "Unable to create instance of " + rawType + "; ReflectionAccessFilter"
+ " does not permit using reflection or Unsafe. Register an InstanceCreator or a TypeAdapter"
+ " for this type or adjust the access filter to allow using reflection.";
return new ObjectConstructor<T>() {
@Override public T construct() {
throw new JsonIOException(message);
@ -219,10 +228,10 @@ public final class ConstructorConstructor {
&& (filterResult != FilterResult.BLOCK_ALL || Modifier.isPublic(constructor.getModifiers())));
if (!canAccess) {
final String message = "Unable to invoke no-args constructor of " + rawType + "; "
+ "constructor is not accessible and ReflectionAccessFilter does not permit making "
+ "it accessible. Register an InstanceCreator or a TypeAdapter for this type, change "
+ "the visibility of the constructor or adjust the access filter.";
final String message = "Unable to invoke no-args constructor of " + rawType + ";"
+ " constructor is not accessible and ReflectionAccessFilter does not permit making"
+ " it accessible. Register an InstanceCreator or a TypeAdapter for this type, change"
+ " the visibility of the constructor or adjust the access filter.";
return new ObjectConstructor<T>() {
@Override public T construct() {
throw new JsonIOException(message);
@ -370,19 +379,29 @@ public final class ConstructorConstructor {
T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType);
return newInstance;
} catch (Exception e) {
throw new RuntimeException(("Unable to create instance of " + rawType + ". "
+ "Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args "
+ "constructor may fix this problem."), e);
throw new RuntimeException(("Unable to create instance of " + rawType + "."
+ " Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args"
+ " constructor may fix this problem."), e);
}
}
};
} else {
final String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe "
+ "is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args "
+ "constructor, or enabling usage of JDK Unsafe may fix this problem.";
String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe"
+ " is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args"
+ " constructor, or enabling usage of JDK Unsafe may fix this problem.";
// Check if R8 removed all constructors
if (rawType.getDeclaredConstructors().length == 0) {
// R8 with Unsafe disabled might not be common enough to warrant a separate Troubleshooting Guide entry
exceptionMessage += " Or adjust your R8 configuration to keep the no-args constructor of the class.";
}
// Explicit final variable to allow usage in the anonymous class below
final String exceptionMessageF = exceptionMessage;
return new ObjectConstructor<T>() {
@Override public T construct() {
throw new JsonIOException(exceptionMessage);
throw new JsonIOException(exceptionMessageF);
}
};
}

View File

@ -5,9 +5,9 @@ import com.google.gson.reflect.*;
public class DefaultConfig {
public static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
public static final boolean DEFAULT_LENIENT = false;
public static final Strictness DEFAULT_STRICTNESS = Strictness.LEGACY_STRICT;
public static final boolean DEFAULT_OMIT_QUOTES = false;
public static final FormattingStyle DEFAULT_FORMATTING_STYLE = null;
public static final FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT;
public static final boolean DEFAULT_ESCAPE_HTML = true;
public static final boolean DEFAULT_SERIALIZE_NULLS = false;
public static final boolean DEFAULT_COMPLEX_MAP_KEYS = false;

View File

@ -45,7 +45,7 @@ public final class JavaVersion {
// Parses both legacy 1.8 style and newer 9.0.4 style
private static int parseDotted(String javaVersion) {
try {
String[] parts = javaVersion.split("[._]");
String[] parts = javaVersion.split("[._]", 3);
int firstVer = Integer.parseInt(parts[0]);
if (firstVer == 1 && parts.length > 1) {
return Integer.parseInt(parts[1]);

View File

@ -33,6 +33,7 @@ public class NonNullElementWrapperList<E> extends AbstractList<E> implements Ran
// Explicitly specify ArrayList as type to guarantee that delegate implements RandomAccess
private final ArrayList<E> delegate;
@SuppressWarnings("NonApiType")
public NonNullElementWrapperList(ArrayList<E> delegate) {
this.delegate = Objects.requireNonNull(delegate);
}

View File

@ -23,6 +23,7 @@ import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
@ -44,7 +45,7 @@ public final class Streams {
public static JsonElement parse(JsonReader reader) throws JsonParseException {
boolean isEmpty = true;
try {
reader.peek();
JsonToken unused = reader.peek();
isEmpty = false;
return TypeAdapters.JSON_ELEMENT.read(reader);
} catch (EOFException e) {

View File

@ -0,0 +1,12 @@
package com.google.gson.internal;
public class TroubleshootingGuide {
private TroubleshootingGuide() {}
/**
* Creates a URL referring to the specified troubleshooting section.
*/
public static String createUrl(String id) {
return "https://github.com/google/gson/blob/main/Troubleshooting.md#" + id;
}
}

View File

@ -17,6 +17,7 @@
package com.google.gson.internal.bind;
import com.google.gson.Gson;
import com.google.gson.Strictness;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
@ -65,7 +66,7 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
in.nextNull();
return null;
}
if (in.isLenient() && in.peek() != JsonToken.BEGIN_ARRAY) {
if (in.getStrictness() == Strictness.LENIENT && in.peek() != JsonToken.BEGIN_ARRAY) {
// Coerce
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, componentTypeAdapter.read(in));

View File

@ -17,6 +17,7 @@
package com.google.gson.internal.bind;
import com.google.gson.Gson;
import com.google.gson.Strictness;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
@ -77,7 +78,7 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
}
Collection<E> collection = constructor.construct();
if (!in.isLenient() || in.peek() == JsonToken.BEGIN_ARRAY) {
if (in.getStrictness() != Strictness.LENIENT || in.peek() == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);

View File

@ -24,6 +24,9 @@ import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.reflect.TypeToken;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Given a type T, looks for the annotation {@link JsonAdapter} and uses an instance of the
@ -32,33 +35,85 @@ import com.google.gson.reflect.TypeToken;
* @since 2.3
*/
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
private static class DummyTypeAdapterFactory implements TypeAdapterFactory {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
throw new AssertionError("Factory should not be used");
}
}
/**
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter}
* on a class.
*/
private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY = new DummyTypeAdapterFactory();
/**
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter}
* on a field.
*/
private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY = new DummyTypeAdapterFactory();
private final ConstructorConstructor constructorConstructor;
/**
* For a class, if it is annotated with {@code @JsonAdapter} and refers to a {@link TypeAdapterFactory},
* stores the factory instance in case it has been requested already.
* Has to be a {@link ConcurrentMap} because {@link Gson} guarantees to be thread-safe.
*/
// Note: In case these strong reference to TypeAdapterFactory instances are considered
// a memory leak in the future, could consider switching to WeakReference<TypeAdapterFactory>
private final ConcurrentMap<Class<?>, TypeAdapterFactory> adapterFactoryMap;
public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
this.constructorConstructor = constructorConstructor;
this.adapterFactoryMap = new ConcurrentHashMap<>();
}
// Separate helper method to make sure callers retrieve annotation in a consistent way
private JsonAdapter getAnnotation(Class<?> rawType) {
return rawType.getAnnotation(JsonAdapter.class);
}
@SuppressWarnings("unchecked") // this is not safe; requires that user has specified correct adapter class for @JsonAdapter
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
Class<? super T> rawType = targetType.getRawType();
JsonAdapter annotation = rawType.getAnnotation(JsonAdapter.class);
JsonAdapter annotation = getAnnotation(rawType);
if (annotation == null) {
return null;
}
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation, true);
}
// Separate helper method to make sure callers create adapter in a consistent way
private static Object createAdapter(ConstructorConstructor constructorConstructor, Class<?> adapterClass) {
// TODO: The exception messages created by ConstructorConstructor are currently written in the context of
// deserialization and for example suggest usage of TypeAdapter, which would not work for @JsonAdapter usage
return constructorConstructor.get(TypeToken.get(adapterClass)).construct();
}
private TypeAdapterFactory putFactoryAndGetCurrent(Class<?> rawType, TypeAdapterFactory factory) {
// Uses putIfAbsent in case multiple threads concurrently create factory
TypeAdapterFactory existingFactory = adapterFactoryMap.putIfAbsent(rawType, factory);
return existingFactory != null ? existingFactory : factory;
}
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
TypeToken<?> type, JsonAdapter annotation) {
Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct();
TypeToken<?> type, JsonAdapter annotation, boolean isClassAnnotation) {
Object instance = createAdapter(constructorConstructor, annotation.value());
TypeAdapter<?> typeAdapter;
boolean nullSafe = annotation.nullSafe();
if (instance instanceof TypeAdapter) {
typeAdapter = (TypeAdapter<?>) instance;
} else if (instance instanceof TypeAdapterFactory) {
typeAdapter = ((TypeAdapterFactory) instance).create(gson, type);
TypeAdapterFactory factory = (TypeAdapterFactory) instance;
if (isClassAnnotation) {
factory = putFactoryAndGetCurrent(type.getRawType(), factory);
}
typeAdapter = factory.create(gson, type);
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
JsonSerializer<?> serializer = instance instanceof JsonSerializer
? (JsonSerializer<?>) instance
@ -67,10 +122,19 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
? (JsonDeserializer<?>) instance
: null;
// Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for `Gson.getDelegateAdapter`
// call and has to differentiate there whether TreeTypeAdapter was created for @JsonAdapter on class or field
TypeAdapterFactory skipPast;
if (isClassAnnotation) {
skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;
} else {
skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null, nullSafe);
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);
typeAdapter = tempAdapter;
// TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()`
nullSafe = false;
} else {
throw new IllegalArgumentException("Invalid attempt to bind an instance of "
@ -85,4 +149,45 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
return typeAdapter;
}
/**
* Returns whether {@code factory} is a type adapter factory created for {@code @JsonAdapter}
* placed on {@code type}.
*/
public boolean isClassJsonAdapterFactory(TypeToken<?> type, TypeAdapterFactory factory) {
Objects.requireNonNull(type);
Objects.requireNonNull(factory);
if (factory == TREE_TYPE_CLASS_DUMMY_FACTORY) {
return true;
}
// Using raw type to match behavior of `create(Gson, TypeToken<T>)` above
Class<?> rawType = type.getRawType();
TypeAdapterFactory existingFactory = adapterFactoryMap.get(rawType);
if (existingFactory != null) {
// Checks for reference equality, like it is done by `Gson.getDelegateAdapter`
return existingFactory == factory;
}
// If no factory has been created for the type yet check manually for a @JsonAdapter annotation
// which specifies a TypeAdapterFactory
// Otherwise behavior would not be consistent, depending on whether or not adapter had been requested
// before call to `isClassJsonAdapterFactory` was made
JsonAdapter annotation = getAnnotation(rawType);
if (annotation == null) {
return false;
}
Class<?> adapterClass = annotation.value();
if (!TypeAdapterFactory.class.isAssignableFrom(adapterClass)) {
return false;
}
Object adapter = createAdapter(constructorConstructor, adapterClass);
TypeAdapterFactory newFactory = (TypeAdapterFactory) adapter;
return putFactoryAndGetCurrent(rawType, newFactory) == factory;
}
}

View File

@ -33,6 +33,7 @@ import com.google.gson.internal.Excluder;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.internal.Primitives;
import com.google.gson.internal.ReflectionAccessFilterHelper;
import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.internal.reflect.ReflectionHelper;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
@ -81,6 +82,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
}
/** first element holds the default name */
@SuppressWarnings("MixedMutabilityReturnType")
private List<String> getFieldNames(Field f) {
SerializedName annotation = f.getAnnotation(SerializedName.class);
if (annotation == null) {
@ -113,7 +115,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
if (filterResult == FilterResult.BLOCK_ALL) {
throw new JsonIOException(
"ReflectionAccessFilter does not permit using reflection for " + raw
+ ". Register a TypeAdapter for this type or adjust the access filter.");
+ ". Register a TypeAdapter for this type or adjust the access filter.");
}
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
@ -154,13 +156,21 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
if (annotation != null) {
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter
mapped = jsonAdapterFactory.getTypeAdapter(
constructorConstructor, context, fieldType, annotation);
constructorConstructor, context, fieldType, annotation, false);
}
final boolean jsonAdapterPresent = mapped != null;
if (mapped == null) mapped = context.getAdapter(fieldType);
@SuppressWarnings("unchecked")
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
final TypeAdapter<Object> writeTypeAdapter;
if (serialize) {
writeTypeAdapter = jsonAdapterPresent ? typeAdapter
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
} else {
// Will never actually be used, but we set it to avoid confusing nullness-analysis tools
writeTypeAdapter = typeAdapter;
}
return new BoundField(name, field, serialize, deserialize) {
@Override void write(JsonWriter writer, Object source)
throws IOException, IllegalAccessException {
@ -191,9 +201,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
return;
}
writer.name(name);
TypeAdapter<Object> t = jsonAdapterPresent ? typeAdapter
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
writeTypeAdapter.write(writer, fieldValue);
}
@Override
@ -299,7 +307,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
if (previous != null) {
throw new IllegalArgumentException("Class " + originalRaw.getName()
+ " declares multiple JSON fields named '" + previous.name + "'; conflict is caused"
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field));
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field)
+ "\nSee " + TroubleshootingGuide.createUrl("duplicate-fields"));
}
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));

View File

@ -43,11 +43,18 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
private final JsonDeserializer<T> deserializer;
final Gson gson;
private final TypeToken<T> typeToken;
private final TypeAdapterFactory skipPast;
/**
* Only intended as {@code skipPast} for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)},
* must not be used in any other way.
*/
private final TypeAdapterFactory skipPastForGetDelegateAdapter;
private final GsonContextImpl context = new GsonContextImpl();
private final boolean nullSafe;
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
/**
* The delegate is lazily created because it may not be needed, and creating it may fail.
* Field has to be {@code volatile} because {@link Gson} guarantees to be thread-safe.
*/
private volatile TypeAdapter<T> delegate;
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
@ -56,7 +63,7 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
this.deserializer = deserializer;
this.gson = gson;
this.typeToken = typeToken;
this.skipPast = skipPast;
this.skipPastForGetDelegateAdapter = skipPast;
this.nullSafe = nullSafe;
}
@ -94,7 +101,7 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
TypeAdapter<T> d = delegate;
return d != null
? d
: (delegate = gson.getDelegateAdapter(skipPast, typeToken));
: (delegate = gson.getDelegateAdapter(skipPastForGetDelegateAdapter, typeToken));
}
/**
@ -174,8 +181,9 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
@Override public JsonElement serialize(Object src, Type typeOfSrc) {
return gson.toJsonTree(src, typeOfSrc);
}
@SuppressWarnings("unchecked")
@Override public <R> R deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
@Override
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
public <R> R deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
return gson.fromJson(json, typeOfT);
}
}

View File

@ -28,6 +28,7 @@ import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.SerializedName;
import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.internal.TroubleshootingGuide;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
@ -72,12 +73,14 @@ public final class TypeAdapters {
@Override
public void write(JsonWriter out, Class value) throws IOException {
throw new UnsupportedOperationException("Attempted to serialize java.lang.Class: "
+ value.getName() + ". Forgot to register a type adapter?");
+ value.getName() + ". Forgot to register a type adapter?"
+ "\nSee " + TroubleshootingGuide.createUrl("java-lang-class-unsupported"));
}
@Override
public Class read(JsonReader in) throws IOException {
throw new UnsupportedOperationException(
"Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?");
"Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?"
+ "\nSee " + TroubleshootingGuide.createUrl("java-lang-class-unsupported"));
}
}.nullSafe();
@ -521,12 +524,8 @@ public final class TypeAdapters {
public static final TypeAdapter<URL> URL = new TypeAdapter<URL>() {
@Override
public URL read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
String nextString = in.nextString();
return "null".equals(nextString) ? null : new URL(nextString);
URI uri = URI.read(in);
return uri == null ? null : uri.toURL();
}
@Override
public void write(JsonWriter out, URL value) throws IOException {
@ -566,7 +565,11 @@ public final class TypeAdapters {
return null;
}
// regrettably, this should have included both the host name and the host address
return InetAddress.getByName(in.nextString());
// For compatibility, we use InetAddress.getByName rather than the possibly-better
// .getAllByName
@SuppressWarnings("AddressSelection")
InetAddress addr = InetAddress.getByName(in.nextString());
return addr;
}
@Override
public void write(JsonWriter out, InetAddress value) throws IOException {

View File

@ -18,6 +18,7 @@ package com.google.gson.internal.reflect;
import com.google.gson.JsonIOException;
import com.google.gson.internal.GsonBuildConfig;
import com.google.gson.internal.TroubleshootingGuide;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@ -32,7 +33,7 @@ public class ReflectionHelper {
try {
// Try to construct the RecordSupportedHelper, if this fails, records are not supported on this JVM.
instance = new RecordSupportedHelper();
} catch (NoSuchMethodException e) {
} catch (ReflectiveOperationException e) {
instance = new RecordNotSupportedHelper();
}
RECORD_HELPER = instance;
@ -40,6 +41,17 @@ public class ReflectionHelper {
private ReflectionHelper() {}
private static String getInaccessibleTroubleshootingSuffix(Exception e) {
// Class was added in Java 9, therefore cannot use instanceof
if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
String message = e.getMessage();
String troubleshootingId = message != null && message.contains("to module com.google.gson")
? "reflection-inaccessible-to-module-gson" : "reflection-inaccessible";
return "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId);
}
return "";
}
/**
* Internal implementation of making an {@link AccessibleObject} accessible.
*
@ -52,7 +64,8 @@ public class ReflectionHelper {
} catch (Exception exception) {
String description = getAccessibleObjectDescription(object, false);
throw new JsonIOException("Failed making " + description + " accessible; either increase its visibility"
+ " or write a custom TypeAdapter for its declaring type.", exception);
+ " or write a custom TypeAdapter for its declaring type." + getInaccessibleTroubleshootingSuffix(exception),
exception);
}
}
@ -142,7 +155,7 @@ public class ReflectionHelper {
return "Failed making constructor '" + constructorToString(constructor) + "' accessible;"
+ " either increase its visibility or write a custom InstanceCreator or TypeAdapter for"
// Include the message since it might contain more detailed information
+ " its declaring type: " + exception.getMessage();
+ " its declaring type: " + exception.getMessage() + getInaccessibleTroubleshootingSuffix(exception);
}
}
@ -202,11 +215,10 @@ public class ReflectionHelper {
private final Method getName;
private final Method getType;
private RecordSupportedHelper() throws NoSuchMethodException {
private RecordSupportedHelper() throws NoSuchMethodException, ClassNotFoundException {
isRecord = Class.class.getMethod("isRecord");
getRecordComponents = Class.class.getMethod("getRecordComponents");
// Class java.lang.reflect.RecordComponent
Class<?> classRecordComponent = getRecordComponents.getReturnType().getComponentType();
Class<?> classRecordComponent = Class.forName("java.lang.reflect.RecordComponent");
getName = classRecordComponent.getMethod("getName");
getType = classRecordComponent.getMethod("getType");
}

View File

@ -36,6 +36,7 @@ import java.util.Date;
* this class state. DateFormat isn't thread safe either, so this class has
* to synchronize its read and write methods.
*/
@SuppressWarnings("JavaUtilDate")
final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal

View File

@ -37,6 +37,7 @@ import java.util.Date;
* this class state. DateFormat isn't thread safe either, so this class has
* to synchronize its read and write methods.
*/
@SuppressWarnings("JavaUtilDate")
final class SqlTimeTypeAdapter extends TypeAdapter<Time> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal

View File

@ -27,6 +27,7 @@ import java.io.IOException;
import java.sql.Timestamp;
import java.util.Date;
@SuppressWarnings("JavaUtilDate")
class SqlTimestampTypeAdapter extends TypeAdapter<Timestamp> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal

View File

@ -33,6 +33,7 @@ import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType;
* it is {@code false} all other constants will be {@code null} and
* there will be no support for {@code java.sql} types.
*/
@SuppressWarnings("JavaUtilDate")
public final class SqlTypesSupport {
/**
* {@code true} if {@code java.sql} types are supported,

View File

@ -17,10 +17,12 @@
package com.google.gson.reflect;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.TroubleshootingGuide;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@ -37,11 +39,12 @@ import java.util.Objects;
* <p>
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
*
* <p>Capturing a type variable as type argument of a {@code TypeToken} should
* be avoided. Due to type erasure the runtime type of a type variable is not
* available to Gson and therefore it cannot provide the functionality one
* might expect, which gives a false sense of type-safety at compilation time
* and can lead to an unexpected {@code ClassCastException} at runtime.
* <p>Capturing a type variable as type argument of an anonymous {@code TypeToken}
* subclass is not allowed, for example {@code TypeToken<List<T>>}.
* Due to type erasure the runtime type of a type variable is not available
* to Gson and therefore it cannot provide the functionality one might expect.
* This would give a false sense of type-safety at compile time and could
* lead to an unexpected {@code ClassCastException} at runtime.
*
* <p>If the type arguments of the parameterized type are only available at
* runtime, for example when you want to create a {@code List<E>} based on
@ -63,7 +66,14 @@ public class TypeToken<T> {
*
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
* parameter in the anonymous class's type hierarchy so we can reconstitute it
* at runtime despite erasure.
* at runtime despite erasure, for example:
* <p>
* {@code new TypeToken<List<String>>() {}}
*
* @throws IllegalArgumentException
* If the anonymous {@code TypeToken} subclass captures a type variable,
* for example {@code TypeToken<List<T>>}. See the {@code TypeToken}
* class documentation for more details.
*/
@SuppressWarnings("unchecked")
protected TypeToken() {
@ -82,6 +92,10 @@ public class TypeToken<T> {
this.hashCode = this.type.hashCode();
}
private static boolean isCapturingTypeVariablesForbidden() {
return !Objects.equals(System.getProperty("gson.allowCapturingTypeVariables"), "true");
}
/**
* Verifies that {@code this} is an instance of a direct subclass of TypeToken and
* returns the type argument for {@code T} in {@link $Gson$Types#canonicalize
@ -92,19 +106,59 @@ public class TypeToken<T> {
if (superclass instanceof ParameterizedType) {
ParameterizedType parameterized = (ParameterizedType) superclass;
if (parameterized.getRawType() == TypeToken.class) {
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
Type typeArgument = $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
if (isCapturingTypeVariablesForbidden()) {
verifyNoTypeVariable(typeArgument);
}
return typeArgument;
}
}
// Check for raw TypeToken as superclass
else if (superclass == TypeToken.class) {
throw new IllegalStateException("TypeToken must be created with a type argument: new TypeToken<...>() {}; "
+ "When using code shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved.");
throw new IllegalStateException("TypeToken must be created with a type argument: new TypeToken<...>() {};"
+ " When using code shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved."
+ "\nSee " + TroubleshootingGuide.createUrl("type-token-raw")
);
}
// User created subclass of subclass of TypeToken
throw new IllegalStateException("Must only create direct subclasses of TypeToken");
}
private static void verifyNoTypeVariable(Type type) {
if (type instanceof TypeVariable) {
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
throw new IllegalArgumentException("TypeToken type argument must not contain a type variable; captured type variable "
+ typeVariable.getName() + " declared by " + typeVariable.getGenericDeclaration()
+ "\nSee " + TroubleshootingGuide.createUrl("typetoken-type-variable"));
} else if (type instanceof GenericArrayType) {
verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type ownerType = parameterizedType.getOwnerType();
if (ownerType != null) {
verifyNoTypeVariable(ownerType);
}
for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
verifyNoTypeVariable(typeArgument);
}
} else if (type instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) type;
for (Type bound : wildcardType.getLowerBounds()) {
verifyNoTypeVariable(bound);
}
for (Type bound : wildcardType.getUpperBounds()) {
verifyNoTypeVariable(bound);
}
} else if (type == null) {
// Occurs in Eclipse IDE and certain Java versions (e.g. Java 11.0.18) when capturing type variable
// declared by method of local class, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975
throw new IllegalArgumentException("TypeToken captured `null` as type argument; probably a compiler / runtime bug");
}
}
/**
* Returns the raw (non-generic) type for this type.
*/
@ -331,9 +385,12 @@ public class TypeToken<T> {
* Class<V> valueClass = ...;
* TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
* }</pre>
* As seen here the result is a {@code TypeToken<?>}; this method cannot provide any type safety,
* 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.
*
* <p>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}, or if the type arguments are invalid for
* the raw type
@ -358,8 +415,19 @@ public class TypeToken<T> {
" 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
if ($Gson$Types.requiresOwnerType(rawType)) {
throw new IllegalArgumentException("Raw type " + rawClass.getName() + " is not supported because"
+ " it requires specifying an owner type");
}
for (int i = 0; i < expectedArgsCount; i++) {
Type typeArgument = typeArguments[i];
Type typeArgument = Objects.requireNonNull(typeArguments[i], "Type argument must not be null");
Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
TypeVariable<?> typeVariable = typeVariables[i];
@ -367,8 +435,8 @@ public class TypeToken<T> {
Class<?> rawBound = $Gson$Types.getRawType(bound);
if (!rawBound.isAssignableFrom(rawTypeArgument)) {
throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds "
+ "for type variable " + typeVariable + " declared by " + rawType);
throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds"
+ " for type variable " + typeVariable + " declared by " + rawType);
}
}
}

View File

@ -16,8 +16,12 @@
package com.google.gson.stream;
import com.google.gson.internal.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.Strictness;
import com.google.gson.internal.DefaultConfig;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.internal.TroubleshootingGuide;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
@ -26,7 +30,7 @@ import java.util.Arrays;
import java.util.Objects;
/**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>)
* Reads a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>)
* encoded value as a stream of tokens. This stream includes both literal
* values (strings, numbers, booleans, and nulls) as well as the begin and
* end delimiters of objects and arrays. The tokens are traversed in
@ -62,6 +66,16 @@ import java.util.Objects;
* Null literals can be consumed using either {@link #nextNull()} or {@link
* #skipValue()}.
*
* <h2>Configuration</h2>
* The behavior of this reader can be customized with the following methods:
* <ul>
* <li>{@link #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT}
* </ul>
*
* The default configuration of {@code JsonReader} instances used internally by
* the {@link Gson} class differs, and can be adjusted with the various
* {@link GsonBuilder} methods.
*
* <h2>Example</h2>
* Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
* [
@ -180,7 +194,7 @@ import java.util.Objects;
* <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable
* by {@code <script>} tags, disarming the attack. Since the prefix is malformed
* JSON, strict parsing fails when it is encountered. This class permits the
* non-execute prefix when {@link #setLenient(boolean) lenient parsing} is
* non-execute prefix when {@linkplain #setStrictness(Strictness) lenient parsing} is
* enabled.
*
* <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
@ -226,8 +240,7 @@ public class JsonReader implements Closeable {
/** The input JSON. */
private final Reader in;
/** True to accept non-spec compliant JSON */
private boolean lenient = DefaultConfig.DEFAULT_LENIENT;
private Strictness strictness = Strictness.LEGACY_STRICT;
private boolean serializeSpecialFloatingPointValues = DefaultConfig.DEFAULT_SPECIALIZE_FLOAT_VALUES;
@ -294,53 +307,101 @@ public class JsonReader implements Closeable {
}
/**
* Configure this parser to be liberal in what it accepts. By default,
* this parser is strict and only accepts JSON as specified by <a
* href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
* parser to lenient causes it to ignore the following syntax errors:
* Sets the strictness of this reader.
*
* <ul>
* <li>Streams that start with the <a href="#nonexecuteprefix">non-execute
* prefix</a>, <code>")]}'\n"</code>.
* <li>Streams that include multiple top-level values. With strict parsing,
* each stream must contain exactly one top-level value.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* <li>End of line comments starting with {@code //} or {@code #} and
* ending with a newline character.
* <li>C-style comments starting with {@code /*} and ending with
* {@code *}{@code /}. Such comments may not be nested.
* <li>Names that are unquoted or {@code 'single quoted'}.
* <li>Strings that are unquoted or {@code 'single quoted'}.
* <li>Array elements separated by {@code ;} instead of {@code ,}.
* <li>Unnecessary array separators. These are ignored.
* <li>Names and values separated by {@code =} or {@code =>} instead of
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
* @deprecated Please use {@link #setStrictness(Strictness)} instead.
* {@code JsonReader.setLenient(true)} should be replaced by {@code JsonReader.setStrictness(Strictness.LENIENT)}
* and {@code JsonReader.setLenient(false)} should be replaced by {@code JsonReader.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
*
* <p>Note: Even in strict mode there are slight derivations from the JSON
* specification:
* <ul>
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null}
* to have any capitalization, for example {@code fAlSe}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '}
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character U+000A), resulting in a {@code LF} within the
* read JSON string
* <li>JsonReader allows unescaped control characters (U+0000 through U+001F)
* </ul>
* @param lenient whether this reader should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
* If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
* @see #setStrictness(Strictness)
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT
public final void setLenient(boolean lenient) {
this.lenient = lenient;
if (lenient) serializeSpecialFloatingPointValues = true;
setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT);
}
/**
* Returns true if this parser is liberal in what it accepts.
* Returns true if the {@link Strictness} of this reader is equal to {@link Strictness#LENIENT}.
*
* @see #setStrictness(Strictness)
*/
public final boolean isLenient() {
return lenient;
return strictness == Strictness.LENIENT;
}
/**
* Configures how liberal this parser is in what it accepts.
*
* <p>In {@linkplain Strictness#STRICT strict} mode, the
* parser only accepts JSON in accordance with <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>.
* In {@linkplain Strictness#LEGACY_STRICT legacy strict} mode (the default), only JSON in accordance with the
* RFC 8259 is accepted, with a few exceptions denoted below for backwards compatibility reasons.
* In {@linkplain Strictness#LENIENT lenient} mode, all sort of non-spec compliant JSON is accepted (see below).</p>
*
* <dl>
* <dt>{@link Strictness#STRICT}</dt>
* <dd>
* In strict mode, only input compliant with RFC 8259 is accepted.
* </dd>
* <dt>{@link Strictness#LEGACY_STRICT}</dt>
* <dd>
* In legacy strict mode, the following departures from RFC 8259 are accepted:
* <ul>
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null}
* to have any capitalization, for example {@code fAlSe} or {@code NULL}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '} (single-quote)
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character {@code U+000A}), resulting in a {@code LF} within the
* read JSON string
* <li>JsonReader allows unescaped control characters ({@code U+0000} through {@code U+001F})
* </ul>
* </dd>
* <dt>{@link Strictness#LENIENT}</dt>
* <dd>
* In lenient mode, all input that is accepted in legacy strict mode is accepted in addition to the following
* departures from RFC 8259:
* <ul>
* <li>Streams that start with the <a href="#nonexecuteprefix">non-execute prefix</a>, {@code ")]}'\n"}
* <li>Streams that include multiple top-level values. With legacy strict or strict parsing,
* each stream must contain exactly one top-level value.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities} represented by
* {@code NaN} and {@code (-)Infinity} respectively.
* <li>End of line comments starting with {@code //} or {@code #} and ending with a newline character.
* <li>C-style comments starting with {@code /*} and ending with
* {@code *}{@code /}. Such comments may not be nested.
* <li>Names that are unquoted or {@code 'single quoted'}.
* <li>Strings that are unquoted or {@code 'single quoted'}.
* <li>Array elements separated by {@code ;} instead of {@code ,}.
* <li>Unnecessary array separators. These are interpreted as if null
* was the omitted value.
* <li>Names and values separated by {@code =} or {@code =>} instead of
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
* </dd>
* </dl>
*
* @param strictness the new strictness value of this reader. May not be {@code null}.
* @since $next-version$
*/
public final void setStrictness(Strictness strictness) {
Objects.requireNonNull(strictness);
this.strictness = strictness;
if (strictness == Strictness.LENIENT) setSerializeSpecialFloatingPointValues(true);
}
/**
* Returns the {@linkplain Strictness strictness} of this reader.
*
* @see #setStrictness(Strictness)
* @since $next-version$
*/
public final Strictness getStrictness() {
return strictness;
}
public void setSerializeSpecialFloatingPointValues(boolean serializeSpecialFloatingPointValues) {
@ -365,7 +426,7 @@ public class JsonReader implements Closeable {
pathIndices[stackSize - 1] = 0;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek() + locationString());
throw unexpectedTokenError("BEGIN_ARRAY");
}
}
@ -383,7 +444,7 @@ public class JsonReader implements Closeable {
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected END_ARRAY but was " + peek() + locationString());
throw unexpectedTokenError("END_ARRAY");
}
}
@ -400,7 +461,7 @@ public class JsonReader implements Closeable {
push(JsonScope.EMPTY_OBJECT);
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek() + locationString());
throw unexpectedTokenError("BEGIN_OBJECT");
}
}
@ -419,7 +480,7 @@ public class JsonReader implements Closeable {
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new IllegalStateException("Expected END_OBJECT but was " + peek() + locationString());
throw unexpectedTokenError("END_OBJECT");
}
}
@ -549,7 +610,7 @@ public class JsonReader implements Closeable {
throw syntaxError("Expected ':'");
}
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
if (lenient) {
if (strictness == Strictness.LENIENT) {
consumeNonExecutePrefix();
}
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
@ -631,6 +692,8 @@ public class JsonReader implements Closeable {
String keyword;
String keywordUpper;
int peeking;
// Look at the first letter to determine what keyword we are trying to match.
if (c == 't' || c == 'T') {
keyword = "true";
keywordUpper = "TRUE";
@ -647,14 +710,18 @@ public class JsonReader implements Closeable {
return PEEKED_NONE;
}
// Confirm that chars [1..length) match the keyword.
// Upper cased keywords are not allowed in STRICT mode
boolean allowsUpperCased = strictness != Strictness.STRICT;
// Confirm that chars [0..length) match the keyword.
int length = keyword.length();
for (int i = 1; i < length; i++) {
for (int i = 0; i < length; i++) {
if (pos + i >= limit && !fillBuffer(i + 1)) {
return PEEKED_NONE;
}
c = buffer[pos + i];
if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) {
boolean matched = c == keyword.charAt(i) || (allowsUpperCased && c == keywordUpper.charAt(i));
if (!matched) {
return PEEKED_NONE;
}
}
@ -819,7 +886,7 @@ public class JsonReader implements Closeable {
} else if (p == PEEKED_DOUBLE_QUOTED_NAME) {
result = nextQuotedValue('"');
} else {
throw new IllegalStateException("Expected a name but was " + peek() + locationString());
throw unexpectedTokenError("a name");
}
peeked = PEEKED_NONE;
pathNames[stackSize - 1] = result;
@ -855,7 +922,7 @@ public class JsonReader implements Closeable {
result = new String(buffer, pos, peekedNumberLength);
pos += peekedNumberLength;
} else {
throw new IllegalStateException("Expected a string but was " + peek() + locationString());
throw unexpectedTokenError("a string");
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
@ -883,7 +950,7 @@ public class JsonReader implements Closeable {
pathIndices[stackSize - 1]++;
return false;
}
throw new IllegalStateException("Expected a boolean but was " + peek() + locationString());
throw unexpectedTokenError("a boolean");
}
/**
@ -902,7 +969,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
} else {
throw new IllegalStateException("Expected null but was " + peek() + locationString());
throw unexpectedTokenError("null");
}
}
@ -915,7 +982,7 @@ public class JsonReader implements Closeable {
* @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 #setLenient(boolean) lenient}.
* and this reader is not {@link #setStrictness(Strictness) lenient}.
*/
public double nextDouble() throws IOException {
int p = peeked;
@ -937,14 +1004,13 @@ public class JsonReader implements Closeable {
} else if (p == PEEKED_UNQUOTED) {
peekedString = nextUnquotedValue();
} else if (p != PEEKED_BUFFERED) {
throw new IllegalStateException("Expected a double but was " + peek() + locationString());
throw unexpectedTokenError("a double");
}
peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
if (!serializeSpecialFloatingPointValues && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new MalformedJsonException(
"JSON forbids NaN and infinities: " + result + locationString());
throw syntaxError("JSON forbids NaN and infinities: " + result);
}
peekedString = null;
peeked = PEEKED_NONE;
@ -992,7 +1058,7 @@ public class JsonReader implements Closeable {
// Fall back to parse as a double below.
}
} else {
throw new IllegalStateException("Expected a long but was " + peek() + locationString());
throw unexpectedTokenError("a long");
}
peeked = PEEKED_BUFFERED;
@ -1029,7 +1095,10 @@ public class JsonReader implements Closeable {
while (p < l) {
int c = buffer[p++];
if (c == quote) {
// In strict mode, throw an exception when meeting unescaped control characters (U+0000 through U+001F)
if (strictness == Strictness.STRICT && c < 0x20) {
throw syntaxError("Unescaped control characters (\\u0000-\\u001F) are not allowed in strict mode");
} else if (c == quote) {
pos = p;
int len = p - start - 1;
if (builder == null) {
@ -1141,7 +1210,7 @@ public class JsonReader implements Closeable {
return;
} else if (c == '\\') {
pos = p;
readEscapeCharacter();
char unused = readEscapeCharacter();
p = pos;
l = limit;
} else if (c == '\n') {
@ -1230,7 +1299,7 @@ public class JsonReader implements Closeable {
// Fall back to parse as a double below.
}
} else {
throw new IllegalStateException("Expected an int but was " + peek() + locationString());
throw unexpectedTokenError("an int");
}
peeked = PEEKED_BUFFERED;
@ -1486,8 +1555,8 @@ public class JsonReader implements Closeable {
}
private void checkLenient() throws IOException {
if (!lenient) {
throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
if (strictness != Strictness.LENIENT) {
throw syntaxError("Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON");
}
}
@ -1610,10 +1679,10 @@ public class JsonReader implements Closeable {
/**
* Unescapes the character identified by the character or characters that
* immediately follow a backslash. The backslash '\' should have already
* been read. This supports both unicode escapes "u000A" and two-character
* been read. This supports both Unicode escapes "u000A" and two-character
* escapes "\n".
*
* @throws NumberFormatException if any unicode escape sequences are
* @throws MalformedJsonException if any Unicode escape sequences are
* malformed.
*/
@SuppressWarnings("fallthrough")
@ -1629,7 +1698,7 @@ public class JsonReader implements Closeable {
throw syntaxError("Unterminated escape sequence");
}
// Equivalent to Integer.parseInt(stringPool.get(buffer, pos, 4), 16);
char result = 0;
int result = 0;
for (int i = pos, end = i + 4; i < end; i++) {
char c = buffer[i];
result <<= 4;
@ -1640,11 +1709,11 @@ public class JsonReader implements Closeable {
} else if (c >= 'A' && c <= 'F') {
result += (c - 'A' + 10);
} else {
throw new NumberFormatException("\\u" + new String(buffer, pos, 4));
throw syntaxError("Malformed Unicode escape \\u" + new String(buffer, pos, 4));
}
}
pos += 4;
return result;
return (char) result;
case 't':
return '\t';
@ -1662,11 +1731,17 @@ public class JsonReader implements Closeable {
return '\f';
case '\n':
if (strictness == Strictness.STRICT) {
throw syntaxError("Cannot escape a newline character in strict mode");
}
lineNumber++;
lineStart = pos;
// fall-through
case '\'':
if (strictness == Strictness.STRICT) {
throw syntaxError("Invalid escaped character \"'\" in strict mode");
}
case '"':
case '\\':
case '/':
@ -1682,7 +1757,16 @@ public class JsonReader implements Closeable {
* with this reader's content.
*/
private IOException syntaxError(String message) throws IOException {
throw new MalformedJsonException(message + locationString());
throw new MalformedJsonException(message + locationString()
+ "\nSee " + TroubleshootingGuide.createUrl("malformed-json"));
}
private IllegalStateException unexpectedTokenError(String expected) throws IOException {
JsonToken peeked = peek();
String troubleshootingId = peeked == JsonToken.NULL
? "adapter-not-null-safe" : "unexpected-json-structure";
return new IllegalStateException("Expected " + expected + " but was " + peek() + locationString()
+ "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId));
}
/**
@ -1690,7 +1774,7 @@ public class JsonReader implements Closeable {
*/
private void consumeNonExecutePrefix() throws IOException {
// fast forward through the leading whitespace
nextNonWhitespace(true);
int unused = nextNonWhitespace(true);
pos--;
if (pos + 5 > limit && !fillBuffer(5)) {
@ -1725,8 +1809,7 @@ public class JsonReader implements Closeable {
} else if (p == PEEKED_UNQUOTED_NAME) {
reader.peeked = PEEKED_UNQUOTED;
} else {
throw new IllegalStateException(
"Expected a name but was " + reader.peek() + reader.locationString());
throw reader.unexpectedTokenError("a name");
}
}
};

View File

@ -138,14 +138,14 @@ public final class JsonTreeWriter extends JsonWriter {
@Override public JsonWriter name(String name) throws IOException {
Objects.requireNonNull(name, "name == null");
if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException();
throw new IllegalStateException("Did not expect a name");
}
JsonElement element = peek();
if (element instanceof JsonObject) {
pendingName = name;
return this;
}
throw new IllegalStateException();
throw new IllegalStateException("Please begin an object before writing a name.");
}
@Override public JsonWriter value(String value) throws IOException {

View File

@ -26,6 +26,10 @@ import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
import com.google.gson.internal.*;
import com.google.gson.FormattingStyle;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.Strictness;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
@ -37,10 +41,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import com.google.gson.FormattingStyle;
/**
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>)
* Writes a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>)
* encoded value to a stream, one token at a time. The stream includes both
* literal values (strings, numbers, booleans and nulls) as well as the begin
* and end delimiters of objects and arrays.
@ -61,6 +63,20 @@ import com.google.gson.FormattingStyle;
* Finally close the object using {@link #endObject()}.
* </ul>
*
* <h2>Configuration</h2>
* The behavior of this writer can be customized with the following methods:
* <ul>
* <li>{@link #setFormattingStyle(FormattingStyle)}, the default is {@link FormattingStyle#COMPACT}
* <li>{@link #setHtmlSafe(boolean)}, by default HTML characters are not escaped
* in the JSON output
* <li>{@link #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT}
* <li>{@link #setSerializeNulls(boolean)}, by default {@code null} is serialized
* </ul>
*
* The default configuration of {@code JsonWriter} instances used internally by
* the {@link Gson} class differs, and can be adjusted with the various
* {@link GsonBuilder} methods.
*
* <h2>Example</h2>
* Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
* [
@ -142,7 +158,7 @@ public class JsonWriter implements Closeable, Flushable {
private static final Pattern VALID_JSON_NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
/*
* From RFC 7159, "All Unicode characters may be placed within the
* From RFC 8259, "All Unicode characters may be placed within the
* quotation marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)."
@ -182,17 +198,14 @@ public class JsonWriter implements Closeable, Flushable {
push(EMPTY_DOCUMENT);
}
/**
* The settings used for pretty printing, or null for no pretty printing.
*/
private FormattingStyle formattingStyle;
// These fields cache data derived from the formatting style, to avoid having to
// re-evaluate it every time something is written
private String formattedColon;
private String formattedComma;
private boolean usesEmptyNewlineAndIndent;
/**
* The name/value separator; either ":" or ": ".
*/
private String separator = ":";
private boolean lenient = DefaultConfig.DEFAULT_LENIENT;
private Strictness strictness = Strictness.LEGACY_STRICT;
private boolean serializeSpecialFloatingPointValues = DefaultConfig.DEFAULT_SPECIALIZE_FLOAT_VALUES;
@ -213,6 +226,7 @@ public class JsonWriter implements Closeable, Flushable {
*/
public JsonWriter(Writer out) {
this.out = Objects.requireNonNull(out, "out == null");
setFormattingStyle(FormattingStyle.COMPACT);
}
/**
@ -221,42 +235,55 @@ public class JsonWriter implements Closeable, Flushable {
* will be compact. Otherwise the encoded document will be more
* human-readable.
*
* <p>This is a convenience method which overwrites any previously
* {@linkplain #setFormattingStyle(FormattingStyle) set formatting style} with
* either {@link FormattingStyle#COMPACT} if the given indent string is
* empty, or {@link FormattingStyle#PRETTY} with the given indent if
* not empty.
*
* @param indent a string containing only whitespace.
*/
public final void setIndent(String indent) {
if (indent.length() == 0) {
setFormattingStyle(null);
if (indent.isEmpty()) {
setFormattingStyle(FormattingStyle.COMPACT);
} else {
setFormattingStyle(FormattingStyle.DEFAULT.withIndent(indent));
setFormattingStyle(FormattingStyle.PRETTY.withIndent(indent));
}
}
/**
* Sets the pretty printing style to be used in the encoded document.
* No pretty printing if null.
* Sets the formatting style to be used in the encoded document.
*
* <p>Sets the various attributes to be used in the encoded document.
* For example the indentation string to be repeated for each level of indentation.
* Or the newline style, to accommodate various OS styles.</p>
* <p>The formatting style specifies for example the indentation string to be
* repeated for each level of indentation, or the newline style, to accommodate
* various OS styles.</p>
*
* <p>Has no effect if the serialized format is a single line.</p>
*
* @param formattingStyle the style used for pretty printing, no pretty printing if null.
* @param formattingStyle the formatting style to use, must not be {@code null}.
* @since $next-version$
*/
public final void setFormattingStyle(FormattingStyle formattingStyle) {
this.formattingStyle = formattingStyle;
if (formattingStyle == null) {
this.separator = ":";
this.formattingStyle = Objects.requireNonNull(formattingStyle);
this.formattedComma = ",";
if (this.formattingStyle.usesSpaceAfterSeparators()) {
this.formattedColon = ": ";
// Only add space if no newline is written
if (this.formattingStyle.getNewline().isEmpty()) {
this.formattedComma = ", ";
}
} else {
this.separator = ": ";
this.formattedColon = ":";
}
this.usesEmptyNewlineAndIndent = this.formattingStyle.getNewline().isEmpty()
&& this.formattingStyle.getIndent().isEmpty();
}
/**
* Returns the pretty printing style used by this writer.
*
* @return the FormattingStyle that will be used.
* @return the {@code FormattingStyle} that will be used.
* @since $next-version$
*/
public final FormattingStyle getFormattingStyle() {
@ -264,25 +291,56 @@ public class JsonWriter implements Closeable, Flushable {
}
/**
* Configure this writer to relax its syntax rules. By default, this writer
* only emits well-formed JSON as specified by <a
* href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer
* to lenient permits the following:
* <ul>
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* </ul>
* Sets the strictness of this writer.
*
* @deprecated Please use {@link #setStrictness(Strictness)} instead.
* {@code JsonWriter.setLenient(true)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LENIENT)}
* and {@code JsonWriter.setLenient(false)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br>
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
*
* @param lenient whether this writer should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
* If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
* @see #setStrictness(Strictness)
*/
@Deprecated
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT
public final void setLenient(boolean lenient) {
this.lenient = lenient;
setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT);
if (lenient) this.serializeSpecialFloatingPointValues = true;
}
/**
* Returns true if this writer has relaxed syntax rules.
* Returns true if the {@link Strictness} of this writer is equal to {@link Strictness#LENIENT}.
*
* @see JsonWriter#setStrictness(Strictness)
*/
public boolean isLenient() {
return lenient;
return strictness == Strictness.LENIENT;
}
/**
* Configures how strict this writer is with regard to the syntax rules specified in <a
* href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. By default, {@link Strictness#LEGACY_STRICT} is used.
*
* <dl>
* <dt>{@link Strictness#STRICT} &amp; {@link Strictness#LEGACY_STRICT}</dt>
* <dd>
* The behavior of these is currently identical. In these strictness modes, the writer only writes JSON
* in accordance with RFC 8259.
* </dd>
* <dt>{@link Strictness#LENIENT}</dt>
* <dd>
* This mode relaxes the behavior of the writer to allow the writing of {@link Double#isNaN() NaNs}
* and {@link Double#isInfinite() infinities}. It also allows writing multiple top level values.
* </dd>
* </dl>
*
* @param strictness the new strictness of this writer. May not be {@code null}.
* @since $next-version$
*/
public final void setStrictness(Strictness strictness) {
this.strictness = Objects.requireNonNull(strictness);
if (strictness == Strictness.LENIENT) setSerializeSpecialFloatingPointValues(true);
}
public void setSerializeSpecialFloatingPointValues(boolean serializeSpecialFloatingPointValues) {
@ -294,7 +352,17 @@ public class JsonWriter implements Closeable, Flushable {
}
/**
* Configure this writer to emit JSON that's safe for direct inclusion in HTML
* Returns the {@linkplain Strictness strictness} of this writer.
*
* @see #setStrictness(Strictness)
* @since $next-version$
*/
public final Strictness getStrictness() {
return strictness;
}
/**
* Configures this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
* {@code &} and {@code =} before writing them to the stream. Without this
* setting, your XML/HTML encoder should replace these characters with the
@ -449,7 +517,7 @@ public class JsonWriter implements Closeable, Flushable {
* This writer MUST be lenient to use this
*/
public JsonWriter comment(String comment) throws IOException {
if (!lenient) throw new MalformedJsonException("Cannot write comment in non-lenient JsonWriter.");
if (strictness != Strictness.LENIENT) throw new MalformedJsonException("Cannot write comment in non-lenient JsonWriter.");
if (comment == null || comment.isBlank()) return this;
String[] parts = comment.split("\n");
Collections.addAll(deferredComment, parts);
@ -479,16 +547,17 @@ public class JsonWriter implements Closeable, Flushable {
/**
* Encodes the property name.
*
* @param name the name of the forthcoming value. May not be null.
* @param name the name of the forthcoming value. May not be {@code null}.
* @return this writer.
*/
public JsonWriter name(String name) throws IOException {
Objects.requireNonNull(name, "name == null");
if (deferredName != null) {
throw new IllegalStateException();
throw new IllegalStateException("Already wrote a name, expecting a value.");
}
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
int context = peek();
if (context != EMPTY_OBJECT && context != NONEMPTY_OBJECT) {
throw new IllegalStateException("Please begin an object before writing a name.");
}
deferredName = name;
return this;
@ -594,12 +663,12 @@ public class JsonWriter implements Closeable, Flushable {
/**
* Encodes {@code value}.
*
* @param value a finite value, or if {@link #setLenient(boolean) lenient},
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
* also {@link Float#isNaN() NaN} or {@link Float#isInfinite()
* infinity}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* #setLenient(boolean) lenient}.
* #setStrictness(Strictness) lenient}.
* @since 2.9.1
*/
public JsonWriter value(float value) throws IOException {
@ -615,11 +684,11 @@ public class JsonWriter implements Closeable, Flushable {
/**
* Encodes {@code value}.
*
* @param value a finite value, or if {@link #setLenient(boolean) lenient},
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}.
* not {@link #setStrictness(Strictness) lenient}.
*/
public JsonWriter value(double value) throws IOException {
writeDeferredName();
@ -658,11 +727,11 @@ public class JsonWriter implements Closeable, Flushable {
* Encodes {@code value}. The value is written by directly writing the {@link Number#toString()}
* result to JSON. Implementations must make sure that the result represents a valid JSON number.
*
* @param value a finite value, or if {@link #setLenient(boolean) lenient},
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}; or if the {@code toString()} result is not a
* not {@link #setStrictness(Strictness) lenient}; or if the {@code toString()} result is not a
* valid JSON number.
*/
public JsonWriter value(Number value) throws IOException {
@ -748,7 +817,7 @@ public class JsonWriter implements Closeable, Flushable {
}
private void newline() throws IOException {
if (formattingStyle == null) {
if (usesEmptyNewlineAndIndent) {
return;
}
@ -765,7 +834,7 @@ public class JsonWriter implements Closeable, Flushable {
private void beforeName() throws IOException {
int context = peek();
if (context == NONEMPTY_OBJECT) { // first in object
out.write(',');
out.write(formattedComma);
} else if (context != EMPTY_OBJECT) { // not in an object!
throw new IllegalStateException("Nesting problem.");
}
@ -786,7 +855,7 @@ public class JsonWriter implements Closeable, Flushable {
private void beforeValue() throws IOException {
switch (peek()) {
case NONEMPTY_DOCUMENT:
if (!lenient) {
if (strictness != Strictness.LENIENT) {
throw new IllegalStateException("JSON must have only one top-level value.");
}
// fall-through
@ -808,7 +877,7 @@ public class JsonWriter implements Closeable, Flushable {
break;
case NONEMPTY_ARRAY: // another in array
out.append(',');
out.append(formattedComma);
newline();
if (!deferredComment.isEmpty()) {
writeDeferredComment();
@ -817,7 +886,7 @@ public class JsonWriter implements Closeable, Flushable {
break;
case DANGLING_NAME: // value for name
out.append(separator);
out.append(formattedColon);
if (!deferredComment.isEmpty()) {
newline();
writeDeferredComment();

View File

@ -16,11 +16,12 @@
package com.google.gson.stream;
import com.google.gson.Strictness;
import java.io.IOException;
/**
* Thrown when a reader encounters malformed JSON. Some syntax errors can be
* ignored by calling {@link JsonReader#setLenient(boolean)}.
* ignored by using {@link Strictness#LENIENT} for {@link JsonReader#setStrictness(Strictness)}.
*/
public final class MalformedJsonException extends IOException {
private static final long serialVersionUID = 1L;

View File

@ -25,15 +25,15 @@ import java.util.Locale;
import java.util.TimeZone;
/**
* Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than using SimpleDateFormat so
* Utilities methods for manipulating dates in iso8601 format. This is much faster and GC friendly than using SimpleDateFormat so
* highly suitable if you (un)serialize lots of date objects.
*
* Supported parse format: [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:]mm]]
*
* @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a>
*/
//Date parsing code from Jackson databind ISO8601Utils.java
// https://github.com/FasterXML/jackson-databind/blob/master/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
// Date parsing code from Jackson databind ISO8601Utils.java
// https://github.com/FasterXML/jackson-databind/blob/2.8/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
public class ISO8601Utils
{
/**

View File

@ -0,0 +1,72 @@
### Gson ProGuard and R8 rules which are relevant for all users
### This file is automatically recognized by ProGuard and R8, see https://developer.android.com/build/shrink-code#configuration-files
###
### IMPORTANT:
### - These rules are additive; don't include anything here which is not specific to Gson (such as completely
### disabling obfuscation for all classes); the user would be unable to disable that then
### - These rules are not complete; users will most likely have to add additional rules for their specific
### classes, for example to disable obfuscation for certain fields or to keep no-args constructors
###
# Keep generic signatures; needed for correct type resolution
-keepattributes Signature
# Keep Gson annotations
# Note: Cannot perform finer selection here to only cover Gson annotations, see also https://stackoverflow.com/q/47515093
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
### The following rules are needed for R8 in "full mode" which only adheres to `-keepattribtues` if
### the corresponding class or field is matches by a `-keep` rule as well, see
### https://r8.googlesource.com/r8/+/refs/heads/main/compatibility-faq.md#r8-full-mode
# Keep class TypeToken (respectively its generic signature) if present
-if class com.google.gson.reflect.TypeToken
-keep,allowobfuscation class com.google.gson.reflect.TypeToken
# Keep any (anonymous) classes extending TypeToken
-keep,allowobfuscation class * extends com.google.gson.reflect.TypeToken
# Keep classes with @JsonAdapter annotation
-keep,allowobfuscation,allowoptimization @com.google.gson.annotations.JsonAdapter class *
# Keep fields with any other Gson annotation
# 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 <fields>;
@com.google.gson.annotations.JsonAdapter <fields>;
@com.google.gson.annotations.Since <fields>;
@com.google.gson.annotations.Until <fields>;
}
# 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
-keepclassmembers class * extends com.google.gson.TypeAdapter {
<init>();
}
-keepclassmembers class * implements com.google.gson.TypeAdapterFactory {
<init>();
}
-keepclassmembers class * implements com.google.gson.JsonSerializer {
<init>();
}
-keepclassmembers class * implements com.google.gson.JsonDeserializer {
<init>();
}
# Keep fields annotated with @SerializedName for classes which are referenced.
# If classes with fields annotated with @SerializedName have a no-args
# constructor keep that as well. 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 class <1> {
@com.google.gson.annotations.SerializedName <fields>;
}
-if class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keepclassmembers,allowobfuscation,allowoptimization class <1> {
<init>();
}

View File

@ -37,6 +37,7 @@ public class DefaultInetAddressTypeAdapterTest {
@Test
public void testInetAddressSerializationAndDeserialization() throws Exception {
@SuppressWarnings("AddressSelection") // we really do want this method
InetAddress address = InetAddress.getByName("8.8.8.8");
String jsonAddress = gson.toJson(address);
assertThat(jsonAddress).isEqualTo("\"8.8.8.8\"");

View File

@ -99,7 +99,7 @@ public class FieldNamingPolicyTest {
try {
// Verify that default Locale has different case conversion rules
assertWithMessage("Test setup is broken")
.that(name.toUpperCase()).doesNotMatch(expected);
.that(name.toUpperCase(Locale.getDefault())).doesNotMatch(expected);
for (FieldNamingPolicy policy : policies) {
// Should ignore default Locale
@ -138,7 +138,7 @@ public class FieldNamingPolicyTest {
try {
// Verify that default Locale has different case conversion rules
assertWithMessage("Test setup is broken")
.that(name.toLowerCase()).doesNotMatch(expected);
.that(name.toLowerCase(Locale.getDefault())).doesNotMatch(expected);
for (FieldNamingPolicy policy : policies) {
// Should ignore default Locale

View File

@ -22,6 +22,8 @@ import static org.junit.Assert.fail;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
@ -224,4 +226,32 @@ public class GsonBuilderTest {
assertThat(e).hasMessageThat().isEqualTo("Invalid version: -0.1");
}
}
@Test
public void testDefaultStrictness() throws IOException {
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test
public void testSetLenient() throws IOException {
GsonBuilder builder = new GsonBuilder();
builder.setLenient();
Gson gson = builder.create();
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness()).isEqualTo(Strictness.LENIENT);
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(Strictness.LENIENT);
}
@Test
public void testSetStrictness() throws IOException {
final Strictness STRICTNESS = Strictness.STRICT;
GsonBuilder builder = new GsonBuilder();
builder.setStrictness(STRICTNESS);
Gson gson = builder.create();
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness()).isEqualTo(STRICTNESS);
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(STRICTNESS);
}
}

View File

@ -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;
@ -59,11 +59,16 @@ public final class GsonTest {
private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE;
private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER;
@Test
public void testStrictnessDefault() {
assertThat(new Gson().strictness).isEqualTo(Strictness.LENIENT);
}
@Test
public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, false, true, false,
FormattingStyle.DEFAULT, true, false, false, true,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false, false,
FormattingStyle.PRETTY, Strictness.LENIENT, false, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -79,8 +84,8 @@ public final class GsonTest {
@Test
public void testClonedTypeAdapterFactoryListsAreIndependent() {
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, false, true, false,
FormattingStyle.DEFAULT, true, false, false, true,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false, false,
FormattingStyle.PRETTY, Strictness.LENIENT, false, false, true,
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
@ -104,12 +109,8 @@ public final class GsonTest {
@Test
public void testGetAdapter_Null() {
Gson gson = new Gson();
try {
gson.getAdapter((TypeToken<?>) null);
fail();
} catch (NullPointerException e) {
assertThat(e).hasMessageThat().isEqualTo("type must not be null");
}
NullPointerException e = assertThrows(NullPointerException.class, () -> gson.getAdapter((TypeToken<?>) null));
assertThat(e).hasMessageThat().isEqualTo("type must not be null");
}
@Test
@ -271,6 +272,90 @@ public final class GsonTest {
assertThat(otherThreadAdapter.get().toJson(null)).isEqualTo("[[\"wrapped-nested\"]]");
}
@Test
public void testGetDelegateAdapter() {
class DummyAdapter extends TypeAdapter<Number> {
private final int number;
DummyAdapter(int number) {
this.number = number;
}
@Override
public Number read(JsonReader in) throws IOException {
throw new AssertionError("not needed for test");
}
@Override
public void write(JsonWriter out, Number value) throws IOException {
throw new AssertionError("not needed for test");
}
// Override toString() for better assertion error messages
@Override
public String toString() {
return "adapter-" + number;
}
}
class DummyFactory implements TypeAdapterFactory {
private final DummyAdapter adapter;
DummyFactory(DummyAdapter adapter) {
this.adapter = adapter;
}
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (TypeAdapter<T>) adapter;
}
// Override equals to verify that reference equality check is performed by Gson,
// and this method is ignored
@Override
public boolean equals(Object obj) {
return obj instanceof DummyFactory && ((DummyFactory) obj).adapter.equals(adapter);
}
@Override
public int hashCode() {
return adapter.hashCode();
}
}
DummyAdapter adapter1 = new DummyAdapter(1);
DummyFactory factory1 = new DummyFactory(adapter1);
DummyAdapter adapter2 = new DummyAdapter(2);
DummyFactory factory2 = new DummyFactory(adapter2);
Gson gson = new GsonBuilder()
// Note: This is 'last in, first out' order; Gson will first use factory2, then factory1
.registerTypeAdapterFactory(factory1)
.registerTypeAdapterFactory(factory2)
.create();
TypeToken<?> type = TypeToken.get(Number.class);
assertThrows(NullPointerException.class, () -> gson.getDelegateAdapter(null, type));
assertThrows(NullPointerException.class, () -> gson.getDelegateAdapter(factory1, null));
// For unknown factory the first adapter for that type should be returned
assertThat(gson.getDelegateAdapter(new DummyFactory(new DummyAdapter(0)), type)).isEqualTo(adapter2);
assertThat(gson.getDelegateAdapter(factory2, type)).isEqualTo(adapter1);
// Default Gson adapter should be returned
assertThat(gson.getDelegateAdapter(factory1, type)).isNotInstanceOf(DummyAdapter.class);
DummyFactory factory1Eq = new DummyFactory(adapter1);
// Verify that test setup is correct
assertThat(factory1.equals(factory1Eq)).isTrue();
// Should only consider reference equality and ignore that custom `equals` method considers
// factories to be equal, therefore returning `adapter2` which came from `factory2` instead
// of skipping past `factory1`
assertThat(gson.getDelegateAdapter(factory1Eq, type)).isEqualTo(adapter2);
}
@Test
public void testNewJsonWriter_Default() throws IOException {
StringWriter writer = new StringWriter();
@ -282,18 +367,15 @@ public final class GsonTest {
jsonWriter.value(true);
jsonWriter.endObject();
try {
// Additional top-level value
jsonWriter.value(1);
fail();
} catch (IllegalStateException expected) {
assertThat(expected).hasMessageThat().isEqualTo("JSON must have only one top-level value.");
}
// Additional top-level value
IllegalStateException e = assertThrows(IllegalStateException.class, () -> jsonWriter.value(1));
assertThat(e).hasMessageThat().isEqualTo("JSON must have only one top-level value.");
jsonWriter.close();
assertThat(writer.toString()).isEqualTo("{\"\\u003ctest2\":true}");
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test
public void testNewJsonWriter_Custom() throws IOException {
StringWriter writer = new StringWriter();
@ -323,14 +405,11 @@ public final class GsonTest {
public void testNewJsonReader_Default() throws IOException {
String json = "test"; // String without quotes
JsonReader jsonReader = new GsonBuilder().create().newJsonReader(new StringReader(json));
try {
jsonReader.nextString();
fail();
} catch (MalformedJsonException expected) {
}
assertThrows(MalformedJsonException.class, jsonReader::nextString);
jsonReader.close();
}
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Test
public void testNewJsonReader_Custom() throws IOException {
String json = "test"; // String without quotes

View File

@ -56,6 +56,7 @@ public class InnerClassExclusionStrategyTest {
assertThat(excluder.excludeField(f, true)).isFalse();
}
@SuppressWarnings("ClassCanBeStatic")
class InnerClass {
}

View File

@ -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.common.MoreAsserts;
import java.util.Arrays;
@ -37,17 +37,8 @@ public class JsonArrayAsListTest {
List<JsonElement> list = a.asList();
assertThat(list.get(0)).isEqualTo(new JsonPrimitive(1));
try {
list.get(-1);
fail();
} catch (IndexOutOfBoundsException e) {
}
try {
list.get(2);
fail();
} catch (IndexOutOfBoundsException e) {
}
assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1));
assertThrows(IndexOutOfBoundsException.class, () -> list.get(2));
a.add((JsonElement) null);
assertThat(list.get(1)).isEqualTo(JsonNull.INSTANCE);
@ -75,24 +66,11 @@ public class JsonArrayAsListTest {
assertThat(list.get(0)).isEqualTo(new JsonPrimitive(2));
assertThat(a.get(0)).isEqualTo(new JsonPrimitive(2));
try {
list.set(-1, new JsonPrimitive(1));
fail();
} catch (IndexOutOfBoundsException e) {
}
assertThrows(IndexOutOfBoundsException.class, () -> list.set(-1, new JsonPrimitive(1)));
assertThrows(IndexOutOfBoundsException.class, () -> list.set(2, new JsonPrimitive(1)));
try {
list.set(2, new JsonPrimitive(1));
fail();
} catch (IndexOutOfBoundsException e) {
}
try {
list.set(0, null);
fail();
} catch (NullPointerException e) {
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
NullPointerException e = assertThrows(NullPointerException.class, () -> list.set(0, null));
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
@Test
@ -115,30 +93,14 @@ public class JsonArrayAsListTest {
);
assertThat(list).isEqualTo(expectedList);
try {
list.set(-1, new JsonPrimitive(1));
fail();
} catch (IndexOutOfBoundsException e) {
}
assertThrows(IndexOutOfBoundsException.class, () -> list.set(-1, new JsonPrimitive(1)));
assertThrows(IndexOutOfBoundsException.class, () -> list.set(list.size(), new JsonPrimitive(1)));
try {
list.set(list.size(), new JsonPrimitive(1));
fail();
} catch (IndexOutOfBoundsException e) {
}
NullPointerException e = assertThrows(NullPointerException.class, () -> list.add(0, null));
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
try {
list.add(0, null);
fail();
} catch (NullPointerException e) {
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
try {
list.add(null);
fail();
} catch (NullPointerException e) {
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
e = assertThrows(NullPointerException.class, () -> list.add(null));
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
@Test
@ -157,18 +119,11 @@ public class JsonArrayAsListTest {
assertThat(list).isEqualTo(expectedList);
assertThat(list).isEqualTo(expectedList);
try {
list.addAll(0, Collections.<JsonElement>singletonList(null));
fail();
} catch (NullPointerException e) {
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
try {
list.addAll(Collections.<JsonElement>singletonList(null));
fail();
} catch (NullPointerException e) {
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
NullPointerException e = assertThrows(NullPointerException.class, () -> list.addAll(0, Collections.<JsonElement>singletonList(null)));
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
e = assertThrows(NullPointerException.class, () -> list.addAll(Collections.<JsonElement>singletonList(null)));
assertThat(e).hasMessageThat().isEqualTo("Element must be non-null");
}
@Test
@ -180,11 +135,8 @@ public class JsonArrayAsListTest {
assertThat(list.remove(0)).isEqualTo(new JsonPrimitive(1));
assertThat(list).hasSize(0);
assertThat(a).hasSize(0);
try {
list.remove(0);
fail();
} catch (IndexOutOfBoundsException e) {
}
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0));
}
@Test

View File

@ -17,7 +17,6 @@
package com.google.gson;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import com.google.common.testing.EqualsTester;
@ -26,6 +25,8 @@ import java.math.BigInteger;
import org.junit.Test;
/**
* Tests handling of JSON arrays.
*
* @author Jesse Wilson
*/
public final class JsonArrayTest {
@ -138,22 +139,19 @@ public final class JsonArrayTest {
jsonArray.getAsBoolean();
fail("expected getBoolean to fail");
} catch (UnsupportedOperationException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("JsonObject");
assertThat(e).hasMessageThat().isEqualTo("JsonObject");
}
try {
jsonArray.get(-1);
fail("expected get to fail");
} catch (IndexOutOfBoundsException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("Index -1 out of bounds for length 1");
assertThat(e).hasMessageThat().isEqualTo("Index -1 out of bounds for length 1");
}
try {
jsonArray.getAsString();
fail("expected getString to fail");
} catch (UnsupportedOperationException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("JsonObject");
assertThat(e).hasMessageThat().isEqualTo("JsonObject");
}
jsonArray.remove(0);
@ -162,36 +160,31 @@ public final class JsonArrayTest {
jsonArray.getAsDouble();
fail("expected getDouble to fail");
} catch (NumberFormatException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
}
try {
jsonArray.getAsInt();
fail("expected getInt to fail");
} catch (NumberFormatException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
}
try {
jsonArray.get(0).getAsJsonArray();
fail("expected getJSONArray to fail");
} catch (IllegalStateException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("Not a JSON Array: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("Not a JSON Array: \"hello\"");
}
try {
jsonArray.getAsJsonObject();
fail("expected getJSONObject to fail");
} catch (IllegalStateException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo( "Not a JSON Object: [\"hello\"]");
assertThat(e).hasMessageThat().isEqualTo("Not a JSON Object: [\"hello\"]");
}
try {
jsonArray.getAsLong();
fail("expected getLong to fail");
} catch (NumberFormatException e) {
assertWithMessage("Expected an exception message")
.that(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
assertThat(e).hasMessageThat().isEqualTo("For input string: \"hello\"");
}
}

View File

@ -22,6 +22,8 @@ import com.google.gson.common.MoreAsserts;
import org.junit.Test;
/**
* Tests handling of JSON nulls.
*
* @author Jesse Wilson
*/
public final class JsonNullTest {

View File

@ -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.common.TestTypes.BagOfPrimitives;
import com.google.gson.internal.Streams;
@ -37,10 +37,7 @@ public class JsonParserTest {
@Test
public void testParseInvalidJson() {
try {
JsonParser.parseString("[[]");
fail();
} catch (JsonSyntaxException expected) { }
assertThrows(JsonSyntaxException.class, () -> JsonParser.parseString("[[]"));
}
@Test
@ -81,11 +78,7 @@ public class JsonParserTest {
@Test
public void testParseUnquotedMultiWordStringFails() {
String unquotedSentence = "Test is a test..blah blah";
try {
JsonParser.parseString(unquotedSentence);
fail();
} catch (JsonSyntaxException expected) { }
assertThrows(JsonSyntaxException.class, () -> JsonParser.parseString("Test is a test..blah blah"));
}
@Test
@ -170,7 +163,7 @@ public class JsonParserTest {
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
JsonReader parser = new JsonReader(reader);
parser.setLenient(true);
parser.setStrictness(Strictness.LENIENT);
JsonElement element1 = Streams.parse(parser);
JsonElement element2 = Streams.parse(parser);
BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class);
@ -178,4 +171,16 @@ public class JsonParserTest {
BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class);
assertThat(actualTwo.stringValue).isEqualTo("two");
}
@Test
public void testStrict() {
JsonReader reader = new JsonReader(new StringReader("faLsE"));
Strictness strictness = Strictness.STRICT;
// Strictness is ignored by JsonParser later; always parses in lenient mode
reader.setStrictness(strictness);
assertThat(JsonParser.parseReader(reader)).isEqualTo(new JsonPrimitive(false));
// Original strictness was restored
assertThat(reader.getStrictness()).isEqualTo(strictness);
}
}

View File

@ -16,7 +16,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 java.io.EOFException;
import java.util.NoSuchElementException;
@ -70,59 +70,36 @@ public class JsonStreamParserTest {
@Test
public void testCallingNextBeyondAvailableInput() {
parser.next();
parser.next();
try {
parser.next();
fail("Parser should not go beyond available input");
} catch (NoSuchElementException expected) {
}
JsonElement unused1 = parser.next();
JsonElement unused2 = parser.next();
// Parser should not go beyond available input
assertThrows(NoSuchElementException.class, parser::next);
}
@Test
public void testEmptyInput() {
JsonStreamParser parser = new JsonStreamParser("");
try {
parser.next();
fail();
} catch (JsonIOException e) {
assertThat(e.getCause()).isInstanceOf(EOFException.class);
}
JsonIOException e = assertThrows(JsonIOException.class, parser::next);
assertThat(e).hasCauseThat().isInstanceOf(EOFException.class);
parser = new JsonStreamParser("");
try {
parser.hasNext();
fail();
} catch (JsonIOException e) {
assertThat(e.getCause()).isInstanceOf(EOFException.class);
}
e = assertThrows(JsonIOException.class, parser::hasNext);
assertThat(e).hasCauseThat().isInstanceOf(EOFException.class);
}
@Test
public void testIncompleteInput() {
JsonStreamParser parser = new JsonStreamParser("[");
assertThat(parser.hasNext()).isTrue();
try {
parser.next();
fail();
} catch (JsonSyntaxException e) {
}
assertThrows(JsonSyntaxException.class, parser::next);
}
@Test
public void testMalformedInput() {
JsonStreamParser parser = new JsonStreamParser(":");
try {
parser.hasNext();
fail();
} catch (JsonSyntaxException e) {
}
assertThrows(JsonSyntaxException.class, parser::hasNext);
parser = new JsonStreamParser(":");
try {
parser.next();
fail();
} catch (JsonSyntaxException e) {
}
assertThrows(JsonSyntaxException.class, parser::next);
}
}

View File

@ -81,6 +81,7 @@ public final class MixedStreamTest {
jsonReader.endArray();
}
@SuppressWarnings("deprecation") // for JsonReader.setLenient
@Test
public void testReaderDoesNotMutateState() throws IOException {
Gson gson = new Gson();
@ -88,14 +89,15 @@ public final class MixedStreamTest {
jsonReader.beginArray();
jsonReader.setLenient(false);
gson.fromJson(jsonReader, Car.class);
Car unused1 = gson.fromJson(jsonReader, Car.class);
assertThat(jsonReader.isLenient()).isFalse();
jsonReader.setLenient(true);
gson.fromJson(jsonReader, Car.class);
Car unused2 = gson.fromJson(jsonReader, Car.class);
assertThat(jsonReader.isLenient()).isTrue();
}
@SuppressWarnings("deprecation") // for JsonWriter.setLenient
@Test
public void testWriteDoesNotMutateState() throws IOException {
Gson gson = new Gson();
@ -220,7 +222,7 @@ public final class MixedStreamTest {
StringWriter writer = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(writer);
new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create()
new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create()
.toJson(doubles, type, jsonWriter);
assertThat(writer.toString()).isEqualTo("[NaN,-Infinity,Infinity,-0.0,0.5,0.0]");

View File

@ -111,7 +111,7 @@ public final class ObjectTypeAdapterTest {
assertThat(actualTimes).isEqualTo(times);
}
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "ClassCanBeStatic"})
private class RuntimeType {
Object a = 5;
Object b = Arrays.asList(1, 2, null);

View File

@ -25,6 +25,8 @@ import java.util.Locale;
import org.junit.Test;
/**
* Tests handling of Core Type Adapters
*
* @author Jesse Wilson
*/
public class OverrideCoreTypeAdaptersTest {

View File

@ -16,6 +16,7 @@
package com.google.gson;
import com.google.common.base.Objects;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.Primitives;
@ -35,7 +36,7 @@ import java.lang.reflect.Type;
*/
public class ParameterizedTypeFixtures {
public static class MyParameterizedType<T> {
public static final class MyParameterizedType<T> {
public final T value;
public MyParameterizedType(T value) {
this.value = value;
@ -80,27 +81,16 @@ public class ParameterizedTypeFixtures {
return value == null ? 0 : value.hashCode();
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
if (!(obj instanceof MyParameterizedType<?>)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MyParameterizedType<T> other = (MyParameterizedType<T>) obj;
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
MyParameterizedType<?> that = (MyParameterizedType<?>) obj;
return Objects.equal(getValue(), that.getValue());
}
}
@ -112,8 +102,6 @@ public class ParameterizedTypeFixtures {
* This means that the fields of the same objects will be overwritten by Gson.
* This is usually fine in tests since there we deserialize just once, but quite
* dangerous in practice.
*
* @param instanceOfT
*/
public MyParameterizedTypeInstanceCreator(T instanceOfT) {
this.instanceOfT = instanceOfT;

View File

@ -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.internal.LazilyParsedNumber;
import com.google.gson.stream.JsonReader;
@ -33,17 +33,14 @@ public class ToNumberPolicyTest {
ToNumberStrategy strategy = ToNumberPolicy.DOUBLE;
assertThat(strategy.readNumber(fromString("10.1"))).isEqualTo(10.1);
assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279"))).isEqualTo(3.141592653589793D);
try {
strategy.readNumber(fromString("1e400"));
fail();
} catch (MalformedJsonException expected) {
assertThat(expected).hasMessageThat().isEqualTo("JSON forbids NaN and infinities: Infinity at line 1 column 6 (char '\0') path $");
}
try {
strategy.readNumber(fromString("\"not-a-number\""));
fail();
} catch (NumberFormatException expected) {
}
MalformedJsonException e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("1e400")));
assertThat(e).hasMessageThat().isEqualTo(
"JSON forbids NaN and infinities: Infinity at line 1 column 6 (char '\0') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json"
);
assertThrows(NumberFormatException.class, () -> strategy.readNumber(fromString("\"not-a-number\"")));
}
@Test
@ -60,40 +57,31 @@ public class ToNumberPolicyTest {
assertThat(strategy.readNumber(fromString("10"))).isEqualTo(10L);
assertThat(strategy.readNumber(fromString("10.1"))).isEqualTo(10.1);
assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279"))).isEqualTo(3.141592653589793D);
try {
strategy.readNumber(fromString("1e400"));
fail();
} catch (MalformedJsonException expected) {
assertThat(expected).hasMessageThat().isEqualTo("JSON forbids NaN and infinities: Infinity; at path $");
}
try {
strategy.readNumber(fromString("\"not-a-number\""));
fail();
} catch (JsonParseException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Cannot parse not-a-number; at path $");
}
Exception e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("1e400")));
assertThat(e).hasMessageThat().isEqualTo("JSON forbids NaN and infinities: Infinity; at path $");
e = assertThrows(JsonParseException.class, () -> strategy.readNumber(fromString("\"not-a-number\"")));
assertThat(e).hasMessageThat().isEqualTo("Cannot parse not-a-number; at path $");
assertThat(strategy.readNumber(fromStringLenient("NaN"))).isEqualTo(Double.NaN);
assertThat(strategy.readNumber(fromStringLenient("Infinity"))).isEqualTo(Double.POSITIVE_INFINITY);
assertThat(strategy.readNumber(fromStringLenient("-Infinity"))).isEqualTo(Double.NEGATIVE_INFINITY);
try {
strategy.readNumber(fromString("NaN"));
fail();
} catch (MalformedJsonException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 (char 'N') path $");
}
try {
strategy.readNumber(fromString("Infinity"));
fail();
} catch (MalformedJsonException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 (char 'I') path $");
}
try {
strategy.readNumber(fromString("-Infinity"));
fail();
} catch (MalformedJsonException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 (char '-') path $");
}
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("NaN")));
assertThat(e).hasMessageThat().isEqualTo(
"Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1 column 1 (char 'N') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("Infinity")));
assertThat(e).hasMessageThat().isEqualTo(
"Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1 column 1 (char 'I') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
e = assertThrows(MalformedJsonException.class, () -> strategy.readNumber(fromString("-Infinity")));
assertThat(e).hasMessageThat().isEqualTo(
"Use JsonReader.setStrictness(Strictness.LENIENT) to accept malformed JSON at line 1 column 1 (char '-') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#malformed-json");
}
@Test
@ -103,36 +91,27 @@ public class ToNumberPolicyTest {
assertThat(strategy.readNumber(fromString("3.141592653589793238462643383279"))).isEqualTo(new BigDecimal("3.141592653589793238462643383279"));
assertThat(strategy.readNumber(fromString("1e400"))).isEqualTo(new BigDecimal("1e400"));
try {
strategy.readNumber(fromString("\"not-a-number\""));
fail();
} catch (JsonParseException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Cannot parse not-a-number; at path $");
}
JsonParseException e = assertThrows(JsonParseException.class, () -> strategy.readNumber(fromString("\"not-a-number\"")));
assertThat(e).hasMessageThat().isEqualTo("Cannot parse not-a-number; at path $");
}
@Test
public void testNullsAreNeverExpected() throws IOException {
try {
ToNumberPolicy.DOUBLE.readNumber(fromString("null"));
fail();
} catch (IllegalStateException expected) {
}
try {
ToNumberPolicy.LAZILY_PARSED_NUMBER.readNumber(fromString("null"));
fail();
} catch (IllegalStateException expected) {
}
try {
ToNumberPolicy.LONG_OR_DOUBLE.readNumber(fromString("null"));
fail();
} catch (IllegalStateException expected) {
}
try {
ToNumberPolicy.BIG_DECIMAL.readNumber(fromString("null"));
fail();
} catch (IllegalStateException expected) {
}
IllegalStateException e = assertThrows(IllegalStateException.class, () -> ToNumberPolicy.DOUBLE.readNumber(fromString("null")));
assertThat(e).hasMessageThat().isEqualTo("Expected a double but was NULL at line 1 column 5 (char '\0') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");
e = assertThrows(IllegalStateException.class, () -> ToNumberPolicy.LAZILY_PARSED_NUMBER.readNumber(fromString("null")));
assertThat(e).hasMessageThat().isEqualTo("Expected a string but was NULL at line 1 column 5 (char '\0') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");
e = assertThrows(IllegalStateException.class, () -> ToNumberPolicy.LONG_OR_DOUBLE.readNumber(fromString("null")));
assertThat(e).hasMessageThat().isEqualTo("Expected a string but was NULL at line 1 column 5 (char '\0') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");
e = assertThrows(IllegalStateException.class, () -> ToNumberPolicy.BIG_DECIMAL.readNumber(fromString("null")));
assertThat(e).hasMessageThat().isEqualTo("Expected a string but was NULL at line 1 column 5 (char '\0') path $"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");
}
private static JsonReader fromString(String json) {
@ -141,7 +120,7 @@ public class ToNumberPolicyTest {
private static JsonReader fromStringLenient(String json) {
JsonReader jsonReader = fromString(json);
jsonReader.setLenient(true);
jsonReader.setStrictness(Strictness.LENIENT);
return jsonReader;
}
}

View File

@ -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.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
@ -59,19 +59,11 @@ public class TypeAdapterTest {
}
};
try {
adapter.toJson(1);
fail();
} catch (JsonIOException e) {
assertThat(e.getCause()).isEqualTo(exception);
}
JsonIOException e = assertThrows(JsonIOException.class, () -> adapter.toJson(1));
assertThat(e).hasCauseThat().isEqualTo(exception);
try {
adapter.toJsonTree(1);
fail();
} catch (JsonIOException e) {
assertThat(e.getCause()).isEqualTo(exception);
}
e = assertThrows(JsonIOException.class, () -> adapter.toJsonTree(1));
assertThat(e).hasCauseThat().isEqualTo(exception);
}
private static final TypeAdapter<String> adapter = new TypeAdapter<String>() {

View File

@ -16,6 +16,7 @@
package com.google.gson.common;
import com.google.common.base.Objects;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@ -146,26 +147,18 @@ public class TestTypes {
}
@Override
public boolean equals(Object obj) {
if (this == obj)
public boolean equals(Object o) {
if (this == o) {
return true;
if (obj == null)
}
if (!(o instanceof BagOfPrimitives)) {
return false;
if (getClass() != obj.getClass())
return false;
BagOfPrimitives other = (BagOfPrimitives) obj;
if (booleanValue != other.booleanValue)
return false;
if (intValue != other.intValue)
return false;
if (longValue != other.longValue)
return false;
if (stringValue == null) {
if (other.stringValue != null)
return false;
} else if (!stringValue.equals(other.stringValue))
return false;
return true;
}
BagOfPrimitives that = (BagOfPrimitives) o;
return longValue == that.longValue
&& getIntValue() == that.getIntValue()
&& booleanValue == that.booleanValue
&& Objects.equal(stringValue, that.stringValue);
}
@Override
@ -233,7 +226,7 @@ public class TestTypes {
// Nothing here..
@Override
public boolean equals(Object other) {
return other.getClass() == ClassWithNoFields.class;
return other instanceof ClassWithNoFields;
}
}

View File

@ -28,7 +28,6 @@ import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;

View File

@ -93,6 +93,7 @@ public class CollectionTest {
}
@Test
@SuppressWarnings("JdkObsolete")
public void testLinkedListSerialization() {
List<String> list = new LinkedList<>();
list.add("a1");
@ -113,6 +114,7 @@ public class CollectionTest {
}
@Test
@SuppressWarnings("JdkObsolete")
public void testQueueSerialization() {
Queue<String> queue = new LinkedList<>();
queue.add("a1");
@ -266,11 +268,11 @@ public class CollectionTest {
}
@Test
public void testRawCollectionDeserializationNotAlllowed() {
public void testRawCollectionDeserializationNotAllowed() {
String json = "[0,1,2,3,4,5,6,7,8,9]";
Collection<?> integers = gson.fromJson(json, Collection.class);
// JsonReader converts numbers to double by default so we need a floating point comparison
assertThat(integers).isEqualTo(Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0));
assertThat(integers).containsExactly(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0).inOrder();
json = "[\"Hello\", \"World\"]";
Collection<?> strings = gson.fromJson(json, Collection.class);
@ -416,9 +418,9 @@ public class CollectionTest {
}
}
private class BigClass { private Map<String, ? extends List<SmallClass>> inBig; }
private static class BigClass { private Map<String, ? extends List<SmallClass>> inBig; }
private class SmallClass { private String inSmall; }
private static class SmallClass { private String inSmall; }
@Test
public void testIssue1107() {

View File

@ -27,7 +27,7 @@ import org.junit.Test;
/**
* Tests for ensuring Gson thread-safety.
*
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
@ -44,23 +44,23 @@ public class ConcurrencyTest {
* http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
*/
@Test
public void testSingleThreadSerialization() {
MyObject myObj = new MyObject();
for (int i = 0; i < 10; i++) {
gson.toJson(myObj);
}
}
public void testSingleThreadSerialization() {
MyObject myObj = new MyObject();
for (int i = 0; i < 10; i++) {
String unused = gson.toJson(myObj);
}
}
/**
* Source-code based on
* http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
*/
@Test
public void testSingleThreadDeserialization() {
for (int i = 0; i < 10; i++) {
gson.fromJson("{\"a\":\"hello\",\"b\":\"world\",\"i\":1}", MyObject.class);
}
}
public void testSingleThreadDeserialization() {
for (int i = 0; i < 10; i++) {
MyObject unused = gson.fromJson("{\"a\":\"hello\",\"b\":\"world\",\"i\":1}", MyObject.class);
}
}
/**
* Source-code based on
@ -79,7 +79,7 @@ public class ConcurrencyTest {
try {
startLatch.await();
for (int i = 0; i < 10; i++) {
gson.toJson(myObj);
String unused = gson.toJson(myObj);
}
} catch (Throwable t) {
failed.set(true);
@ -110,7 +110,7 @@ public class ConcurrencyTest {
try {
startLatch.await();
for (int i = 0; i < 10; i++) {
gson.fromJson("{\"a\":\"hello\",\"b\":\"world\",\"i\":1}", MyObject.class);
MyObject unused = gson.fromJson("{\"a\":\"hello\",\"b\":\"world\",\"i\":1}", MyObject.class);
}
} catch (Throwable t) {
failed.set(true);
@ -124,7 +124,7 @@ public class ConcurrencyTest {
finishedLatch.await();
assertThat(failed.get()).isFalse();
}
@SuppressWarnings("unused")
private static class MyObject {
String a;

View File

@ -18,13 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.*;
import com.google.gson.common.TestTypes.Base;
import com.google.gson.common.TestTypes.ClassWithBaseField;
import java.lang.reflect.Type;
@ -116,7 +110,7 @@ public class CustomDeserializerTest {
public void testJsonTypeFieldBasedDeserialization() {
String json = "{field1:'abc',field2:'def',__type__:'SUB_TYPE1'}";
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(MyBase.class, (JsonDeserializer<MyBase>) (json1, pojoType, context) -> {
String type = json1.getAsJsonObject().get(MyBase.TYPE_ACCESS).getAsString();
return context.deserialize(json1, SubTypes.valueOf(type).getSubclass());
@ -129,6 +123,7 @@ public class CustomDeserializerTest {
static final String TYPE_ACCESS = "__type__";
}
@SuppressWarnings("ImmutableEnumChecker")
private enum SubTypes {
SUB_TYPE1(SubType1.class),
SUB_TYPE2(SubType2.class);
@ -153,7 +148,7 @@ public class CustomDeserializerTest {
@Test
public void testCustomDeserializerReturnsNullForTopLevelObject() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (JsonDeserializer<Base>) (json, typeOfT, context) -> null).create();
String json = "{baseName:'Base',subName:'SubRevised'}";
Base target = gson.fromJson(json, Base.class);
@ -163,7 +158,7 @@ public class CustomDeserializerTest {
@Test
public void testCustomDeserializerReturnsNull() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (JsonDeserializer<Base>) (json, typeOfT, context) -> null).create();
String json = "{base:{baseName:'Base',subName:'SubRevised'}}";
ClassWithBaseField target = gson.fromJson(json, ClassWithBaseField.class);
@ -173,7 +168,7 @@ public class CustomDeserializerTest {
@Test
public void testCustomDeserializerReturnsNullForArrayElements() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (JsonDeserializer<Base>) (json, typeOfT, context) -> null).create();
String json = "[{baseName:'Base'},{baseName:'Base'}]";
Base[] target = gson.fromJson(json, Base[].class);
@ -184,7 +179,7 @@ public class CustomDeserializerTest {
@Test
public void testCustomDeserializerReturnsNullForArrayElementsForArrayField() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (JsonDeserializer<Base>) (json, typeOfT, context) -> null).create();
String json = "{bases:[{baseName:'Base'},{baseName:'Base'}]}";
ClassWithBaseArray target = gson.fromJson(json, ClassWithBaseArray.class);

View File

@ -17,17 +17,8 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.common.base.Splitter;
import com.google.gson.*;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassWithCustomTypeConverter;
import com.google.gson.reflect.TypeToken;
@ -35,6 +26,7 @@ import java.lang.reflect.Type;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
@ -291,9 +283,9 @@ public class CustomTypeAdaptersTest {
String part2;
public StringHolder(String string) {
String[] parts = string.split(":");
part1 = parts[0];
part2 = parts[1];
List<String> parts = Splitter.on(':').splitToList(string);
part1 = parts.get(0);
part2 = parts.get(1);
}
public StringHolder(String part1, String part2) {
this.part1 = part1;
@ -416,7 +408,7 @@ public class CustomTypeAdaptersTest {
@Test
public void testEnsureCustomDeserializerNotInvokedForNullValues() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(DataHolder.class, new DataHolderDeserializer())
.create();
String json = "{wrappedData:null}";
@ -426,6 +418,7 @@ public class CustomTypeAdaptersTest {
// Test created from Issue 352
@Test
@SuppressWarnings({"JavaUtilDate", "UndefinedEquals"})
public void testRegisterHierarchyAdapterForDate() {
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(Date.class, new DateTypeAdapter())
@ -474,6 +467,7 @@ public class CustomTypeAdaptersTest {
}
}
@SuppressWarnings("JavaUtilDate")
private static class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {

View File

@ -18,18 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.*;
import com.google.gson.internal.JavaVersion;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
@ -68,6 +57,7 @@ import org.junit.Test;
* @author Inderjeet Singh
* @author Joel Leitch
*/
@SuppressWarnings("JavaUtilDate")
public class DefaultTypeAdaptersTest {
private Gson gson;
private TimeZone oldTimeZone;
@ -94,6 +84,8 @@ public class DefaultTypeAdaptersTest {
gson.toJson(String.class);
fail();
} catch (UnsupportedOperationException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Attempted to serialize java.lang.Class: java.lang.String. Forgot to register a type adapter?"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#java-lang-class-unsupported");
}
// Override with a custom type adapter for class.
gson = new GsonBuilder().registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create();
@ -106,9 +98,11 @@ public class DefaultTypeAdaptersTest {
gson.fromJson("String.class", Class.class);
fail();
} catch (UnsupportedOperationException expected) {
assertThat(expected).hasMessageThat().isEqualTo("Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#java-lang-class-unsupported");
}
// Override with a custom type adapter for class.
gson = new GsonBuilder().setLenient().registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create();
gson = new GsonBuilder().setStrictness(Strictness.LENIENT).registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create();
assertThat(gson.fromJson("java.lang.String", Class.class)).isAssignableTo(String.class);
}
@ -123,11 +117,11 @@ public class DefaultTypeAdaptersTest {
public void testUrlDeserialization() {
String urlValue = "http://google.com/";
String json = "'http:\\/\\/google.com\\/'";
URL target = gson.fromJson(json, URL.class);
assertThat(target.toExternalForm()).isEqualTo(urlValue);
URL target1 = gson.fromJson(json, URL.class);
assertThat(target1.toExternalForm()).isEqualTo(urlValue);
gson.fromJson('"' + urlValue + '"', URL.class);
assertThat(target.toExternalForm()).isEqualTo(urlValue);
URL target2 = gson.fromJson('"' + urlValue + '"', URL.class);
assertThat(target2.toExternalForm()).isEqualTo(urlValue);
}
@Test
@ -364,7 +358,7 @@ public class DefaultTypeAdaptersTest {
gson.fromJson("[1, []]", BitSet.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("Invalid bitset value type: BEGIN_ARRAY; at path $[1]");
assertThat(e).hasMessageThat().isEqualTo("Invalid bitset value type: BEGIN_ARRAY; at path $[1]");
}
try {
@ -379,11 +373,7 @@ public class DefaultTypeAdaptersTest {
public void testDefaultDateSerialization() {
Date now = new Date(1315806903103L);
String json = gson.toJson(now);
if (JavaVersion.isJava9OrLater()) {
assertThat(json).isEqualTo("\"Sep 11, 2011, 10:55:03 PM\"");
} else {
assertThat(json).isEqualTo("\"Sep 11, 2011 10:55:03 PM\"");
}
assertThat(json).matches("\"Sep 11, 2011,? 10:55:03\\hPM\"");
}
@Test
@ -415,11 +405,7 @@ public class DefaultTypeAdaptersTest {
Gson gson = new GsonBuilder().create();
Date now = new Date(1315806903103L);
String json = gson.toJson(now);
if (JavaVersion.isJava9OrLater()) {
assertThat(json).isEqualTo("\"Sep 11, 2011, 10:55:03 PM\"");
} else {
assertThat(json).isEqualTo("\"Sep 11, 2011 10:55:03 PM\"");
}
assertThat(json).matches("\"Sep 11, 2011,? 10:55:03\\hPM\"");
}
@Test
@ -630,7 +616,7 @@ public class DefaultTypeAdaptersTest {
gson.fromJson("\"abc\"", JsonObject.class);
fail();
} catch (JsonSyntaxException expected) {
assertThat(expected.getMessage()).isEqualTo("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive; at path $");
assertThat(expected).hasMessageThat().isEqualTo("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive; at path $");
}
}
@ -686,6 +672,7 @@ public class DefaultTypeAdaptersTest {
assertThat(treeSet).contains("Value1");
}
@SuppressWarnings("UnnecessaryStringBuilder") // TODO: b/287969247 - remove when EP bug fixed
@Test
public void testStringBuilderSerialization() {
StringBuilder sb = new StringBuilder("abc");
@ -700,6 +687,7 @@ public class DefaultTypeAdaptersTest {
}
@Test
@SuppressWarnings("JdkObsolete")
public void testStringBufferSerialization() {
StringBuffer sb = new StringBuffer("abc");
String json = gson.toJson(sb);

View File

@ -56,7 +56,7 @@ public class DelegateTypeAdapterTest {
bags.add(new BagOfPrimitives(i, i, i % 2 == 0, String.valueOf(i)));
}
String json = gson.toJson(bags);
bags = gson.fromJson(json, new TypeToken<List<BagOfPrimitives>>(){}.getType());
gson.fromJson(json, new TypeToken<List<BagOfPrimitives>>(){}.getType());
// 11: 1 list object, and 10 entries. stats invoked on all 5 fields
assertThat(stats.numReads).isEqualTo(51);
assertThat(stats.numWrites).isEqualTo(51);
@ -66,7 +66,7 @@ public class DelegateTypeAdapterTest {
public void testDelegateInvokedOnStrings() {
String[] bags = {"1", "2", "3", "4"};
String json = gson.toJson(bags);
bags = gson.fromJson(json, String[].class);
gson.fromJson(json, String[].class);
// 1 array object with 4 elements.
assertThat(stats.numReads).isEqualTo(5);
assertThat(stats.numWrites).isEqualTo(5);

View File

@ -120,8 +120,9 @@ public class EnumTest {
* Test for issue 226.
*/
@Test
@SuppressWarnings("GetClassOnEnum")
public void testEnumSubclass() {
assertThat(Roshambo.ROCK.getClass()).isAssignableTo(Roshambo.class);
assertThat(Roshambo.ROCK.getClass()).isNotEqualTo(Roshambo.class);
assertThat(gson.toJson(Roshambo.ROCK)).isEqualTo("\"ROCK\"");
assertThat(gson.toJson(EnumSet.allOf(Roshambo.class))).isEqualTo("[\"ROCK\",\"PAPER\",\"SCISSORS\"]");
assertThat(gson.fromJson("\"ROCK\"", Roshambo.class)).isEqualTo(Roshambo.ROCK);
@ -131,11 +132,12 @@ public class EnumTest {
}
@Test
@SuppressWarnings("GetClassOnEnum")
public void testEnumSubclassWithRegisteredTypeAdapter() {
gson = new GsonBuilder()
.registerTypeHierarchyAdapter(Roshambo.class, new MyEnumTypeAdapter())
.create();
assertThat(Roshambo.ROCK.getClass()).isAssignableTo(Roshambo.class);
assertThat(Roshambo.ROCK.getClass()).isNotEqualTo(Roshambo.class);
assertThat(gson.toJson(Roshambo.ROCK)).isEqualTo("\"123ROCK\"");
assertThat(gson.toJson(EnumSet.allOf(Roshambo.class))).isEqualTo("[\"123ROCK\",\"123PAPER\",\"123SCISSORS\"]");
assertThat(gson.fromJson("\"123ROCK\"", Roshambo.class)).isEqualTo(Roshambo.ROCK);
@ -207,6 +209,7 @@ public class EnumTest {
}
};
@SuppressWarnings("unused")
abstract Roshambo defeats();
}
@ -239,8 +242,8 @@ public class EnumTest {
private enum Color {
RED("red", 1), BLUE("blue", 2), GREEN("green", 3);
String value;
int index;
final String value;
final int index;
private Color(String value, int index) {
this.value = value;
this.index = index;

View File

@ -70,7 +70,7 @@ public class EscapingTest {
assertThat(jsonRepresentation).contains("\\\"");
BagOfPrimitives expectedObject = gson.fromJson(jsonRepresentation, BagOfPrimitives.class);
assertThat(expectedObject.getExpectedJson()).isEqualTo(objWithPrimitives.getExpectedJson());
assertThat(objWithPrimitives.getExpectedJson()).isEqualTo(expectedObject.getExpectedJson());
}
@Test

View File

@ -18,9 +18,11 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.errorprone.annotations.Keep;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.Strictness;
import com.google.gson.annotations.Expose;
import java.lang.reflect.Type;
import org.junit.Before;
@ -38,7 +40,7 @@ public class ExposeFieldsTest {
@Before
public void setUp() throws Exception {
gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapter(SomeInterface.class, new SomeInterfaceInstanceCreator())
.create();
@ -60,11 +62,14 @@ public class ExposeFieldsTest {
ClassWithExposedFields[] objects = { object1, object2, object3 };
String json = gson.toJson(objects);
String expected = new StringBuilder()
.append('[').append(object1.getExpectedJson()).append(',')
.append(object2.getExpectedJson()).append(',')
.append(object3.getExpectedJson()).append(']')
.toString();
String expected =
'['
+ object1.getExpectedJson()
+ ','
+ object2.getExpectedJson()
+ ','
+ object3.getExpectedJson()
+ ']';
assertThat(json).isEqualTo(expected);
}
@ -122,9 +127,14 @@ public class ExposeFieldsTest {
private static class ClassWithExposedFields {
@Expose private final Integer a;
private final Integer b;
@Expose(serialize = false) final long c;
@Expose(deserialize = false) final double d;
@Expose(serialize = false, deserialize = false) final char e;
@Expose(serialize = false)
@Keep
final long c;
@Expose(deserialize = false)
final double d;
@Expose(serialize = false, deserialize = false)
@Keep
final char e;
public ClassWithExposedFields(Integer a, Integer b) {
this(a, b, 1L, 2.0, 'a');

View File

@ -75,6 +75,8 @@ public class FieldExclusionTest {
}
private static class Outer {
@SuppressWarnings("ClassCanBeStatic")
private class Inner extends NestedClass {
public Inner(String value) {
super(value);

View File

@ -15,13 +15,17 @@
*/
package com.google.gson.functional;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.gson.FormattingStyle;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@ -34,119 +38,168 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class FormattingStyleTest {
private static final String[] INPUT = {"v1", "v2"};
private static final String EXPECTED = "[<EOL><INDENT>\"v1\",<EOL><INDENT>\"v2\"<EOL>]";
private static final String EXPECTED_OS = buildExpected(System.lineSeparator(), " ");
private static final String EXPECTED_CR = buildExpected("\r", " ");
private static final String EXPECTED_LF = buildExpected("\n", " ");
private static final String EXPECTED_CRLF = buildExpected("\r\n", " ");
// Create new input object every time to protect against tests accidentally modifying input
private static Map<String, List<Integer>> createInput() {
Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2));
return map;
}
private static String buildExpected(String newline, String indent, boolean spaceAfterSeparators) {
String expected = "{<EOL><INDENT>\"a\":<COLON_SPACE>[<EOL><INDENT><INDENT>1,<COMMA_SPACE><EOL><INDENT><INDENT>2<EOL><INDENT>]<EOL>}";
String commaSpace = spaceAfterSeparators && newline.isEmpty() ? " " : "";
return expected.replace("<EOL>", newline).replace("<INDENT>", indent)
.replace("<COLON_SPACE>", spaceAfterSeparators ? " " : "")
.replace("<COMMA_SPACE>", commaSpace);
}
// Various valid strings that can be used for newline and indent
private static final String[] TEST_NEWLINES = {
"", "\r", "\n", "\r\n", "\n\r\r\n", System.lineSeparator()
};
private static final String[] TEST_INDENTS = {
"", " ", " ", " ", "\t", " \t \t"
"", " ", " ", "\t", " \t \t"
};
@Test
public void testDefault() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(INPUT);
// Make sure the default uses LF, like before.
assertEquals(EXPECTED_LF, json);
}
@Test
public void testNewlineCrLf() {
FormattingStyle style = FormattingStyle.DEFAULT.withNewline("\r\n");
Gson gson = new GsonBuilder().setPrettyPrinting(style).create();
String json = gson.toJson(INPUT);
assertEquals(EXPECTED_CRLF, json);
}
@Test
public void testNewlineLf() {
FormattingStyle style = FormattingStyle.DEFAULT.withNewline("\n");
Gson gson = new GsonBuilder().setPrettyPrinting(style).create();
String json = gson.toJson(INPUT);
assertEquals(EXPECTED_LF, json);
}
@Test
public void testNewlineCr() {
FormattingStyle style = FormattingStyle.DEFAULT.withNewline("\r");
Gson gson = new GsonBuilder().setPrettyPrinting(style).create();
String json = gson.toJson(INPUT);
assertEquals(EXPECTED_CR, json);
}
@Test
public void testNewlineOs() {
FormattingStyle style = FormattingStyle.DEFAULT.withNewline(System.lineSeparator());
Gson gson = new GsonBuilder().setPrettyPrinting(style).create();
String json = gson.toJson(INPUT);
assertEquals(EXPECTED_OS, json);
}
@Test
public void testVariousCombinationsToString() {
for (String indent : TEST_INDENTS) {
for (String newline : TEST_NEWLINES) {
FormattingStyle style = FormattingStyle.DEFAULT.withNewline(newline).withIndent(indent);
Gson gson = new GsonBuilder().setPrettyPrinting(style).create();
String json = gson.toJson(INPUT);
assertEquals(buildExpected(newline, indent), json);
}
}
String json = gson.toJson(createInput());
assertThat(json).isEqualTo(buildExpected("\n", " ", true));
}
@Test
public void testVariousCombinationsParse() {
// Mixing various indent and newline styles in the same string, to be parsed.
String jsonStringMix = "[\r\t'v1',\r\n 'v2'\n]";
String jsonStringMix = "{\r\t\"a\":\r\n[ 1,2\t]\n}";
TypeToken<Map<String, List<Integer>>> inputType = new TypeToken<Map<String, List<Integer>>>() {};
String[] actualParsed;
Map<String, List<Integer>> actualParsed;
// Test all that all combinations of newline can be parsed and generate the same INPUT.
for (String indent : TEST_INDENTS) {
for (String newline : TEST_NEWLINES) {
FormattingStyle style = FormattingStyle.DEFAULT.withNewline(newline).withIndent(indent);
Gson gson = new GsonBuilder().setLenient().setPrettyPrinting(style).create();
FormattingStyle style = FormattingStyle.PRETTY.withNewline(newline).withIndent(indent);
Gson gson = new GsonBuilder().setFormattingStyle(style).create();
String toParse = buildExpected(newline, indent);
actualParsed = gson.fromJson(toParse, INPUT.getClass());
assertArrayEquals(INPUT, actualParsed);
String toParse = buildExpected(newline, indent, true);
actualParsed = gson.fromJson(toParse, inputType);
assertThat(actualParsed).isEqualTo(createInput());
// Parse the mixed string with the gson parsers configured with various newline / indents.
actualParsed = gson.fromJson(jsonStringMix, INPUT.getClass());
assertArrayEquals(INPUT, actualParsed);
actualParsed = gson.fromJson(jsonStringMix, inputType);
assertThat(actualParsed).isEqualTo(createInput());
}
}
}
private static String toJson(Object obj, FormattingStyle style) {
return new GsonBuilder().setFormattingStyle(style).create().toJson(obj);
}
@Test
public void testFormatCompact() {
String json = toJson(createInput(), FormattingStyle.COMPACT);
String expectedJson = buildExpected("", "", false);
assertThat(json).isEqualTo(expectedJson);
// Sanity check to verify that `buildExpected` works correctly
assertThat(json).isEqualTo("{\"a\":[1,2]}");
}
@Test
public void testFormatPretty() {
String json = toJson(createInput(), FormattingStyle.PRETTY);
String expectedJson = buildExpected("\n", " ", true);
assertThat(json).isEqualTo(expectedJson);
// Sanity check to verify that `buildExpected` works correctly
assertThat(json).isEqualTo(
"{\n"
+ " \"a\": [\n"
+ " 1,\n"
+ " 2\n"
+ " ]\n"
+ "}");
}
@Test
public void testFormatPrettySingleLine() {
FormattingStyle style = FormattingStyle.COMPACT.withSpaceAfterSeparators(true);
String json = toJson(createInput(), style);
String expectedJson = buildExpected("", "", true);
assertThat(json).isEqualTo(expectedJson);
// Sanity check to verify that `buildExpected` works correctly
assertThat(json).isEqualTo("{\"a\": [1, 2]}");
}
@Test
public void testFormat() {
for (String newline : TEST_NEWLINES) {
for (String indent : TEST_INDENTS) {
for (boolean spaceAfterSeparators : new boolean[] {true, false}) {
FormattingStyle style = FormattingStyle.COMPACT.withNewline(newline)
.withIndent(indent).withSpaceAfterSeparators(spaceAfterSeparators);
String json = toJson(createInput(), style);
String expectedJson = buildExpected(newline, indent, spaceAfterSeparators);
assertThat(json).isEqualTo(expectedJson);
}
}
}
}
/**
* Should be able to convert {@link FormattingStyle#COMPACT} to {@link FormattingStyle#PRETTY}
* using the {@code withX} methods.
*/
@Test
public void testCompactToPretty() {
FormattingStyle style = FormattingStyle.COMPACT.withNewline("\n").withIndent(" ")
.withSpaceAfterSeparators(true);
String json = toJson(createInput(), style);
String expectedJson = toJson(createInput(), FormattingStyle.PRETTY);
assertThat(json).isEqualTo(expectedJson);
}
/**
* Should be able to convert {@link FormattingStyle#PRETTY} to {@link FormattingStyle#COMPACT}
* using the {@code withX} methods.
*/
@Test
public void testPrettyToCompact() {
FormattingStyle style = FormattingStyle.PRETTY.withNewline("").withIndent("")
.withSpaceAfterSeparators(false);
String json = toJson(createInput(), style);
String expectedJson = toJson(createInput(), FormattingStyle.COMPACT);
assertThat(json).isEqualTo(expectedJson);
}
@Test
public void testStyleValidations() {
try {
// TBD if we want to accept \u2028 and \u2029. For now we don't.
FormattingStyle.DEFAULT.withNewline("\u2028");
// TBD if we want to accept \u2028 and \u2029. For now we don't because JSON specification
// does not consider them to be newlines
FormattingStyle.PRETTY.withNewline("\u2028");
fail("Gson should not accept anything but \\r and \\n for newline");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat()
.isEqualTo("Only combinations of \\n and \\r are allowed in newline.");
}
try {
FormattingStyle.DEFAULT.withNewline("NL");
FormattingStyle.PRETTY.withNewline("NL");
fail("Gson should not accept anything but \\r and \\n for newline");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat()
.isEqualTo("Only combinations of \\n and \\r are allowed in newline.");
}
try {
FormattingStyle.DEFAULT.withIndent("\f");
FormattingStyle.PRETTY.withIndent("\f");
fail("Gson should not accept anything but space and tab for indent");
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat()
.isEqualTo("Only combinations of spaces and tabs are allowed in indent.");
}
}
private static String buildExpected(String newline, String indent) {
return EXPECTED.replace("<EOL>", newline).replace("<INDENT>", indent);
}
}

View File

@ -16,14 +16,13 @@
package com.google.gson.functional;
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;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.regex.Pattern;
import org.junit.Before;
import org.junit.Test;
@ -46,7 +45,7 @@ public class GsonVersionDiagnosticsTest {
@Override public void write(JsonWriter out, TestType value) {
throw new AssertionError("Expected during serialization");
}
@Override public TestType read(JsonReader in) throws IOException {
@Override public TestType read(JsonReader in) {
throw new AssertionError("Expected during deserialization");
}
}).create();
@ -54,28 +53,22 @@ public class GsonVersionDiagnosticsTest {
@Test
public void testVersionPattern() {
assertThat(GSON_VERSION_PATTERN.matcher("(GSON 2.8.5)").matches()).isTrue();
assertThat(GSON_VERSION_PATTERN.matcher("(GSON 2.8.5-SNAPSHOT)").matches()).isTrue();
assertThat("(GSON 2.8.5)").matches(GSON_VERSION_PATTERN);
assertThat("(GSON 2.8.5-SNAPSHOT)").matches(GSON_VERSION_PATTERN);
}
@Test
public void testAssertionErrorInSerializationPrintsVersion() {
try {
gson.toJson(new TestType());
fail();
} catch (AssertionError expected) {
ensureAssertionErrorPrintsGsonVersion(expected);
}
AssertionError e = assertThrows(AssertionError.class, () -> gson.toJson(new TestType()));
ensureAssertionErrorPrintsGsonVersion(e);
}
@Test
public void testAssertionErrorInDeserializationPrintsVersion() {
try {
gson.fromJson("{'a':'abc'}", TestType.class);
fail();
} catch (AssertionError expected) {
ensureAssertionErrorPrintsGsonVersion(expected);
}
AssertionError e = assertThrows(AssertionError.class,
() -> gson.fromJson("{'a':'abc'}", TestType.class));
ensureAssertionErrorPrintsGsonVersion(e);
}
private void ensureAssertionErrorPrintsGsonVersion(AssertionError expected) {
@ -87,7 +80,7 @@ public class GsonVersionDiagnosticsTest {
assertThat(end > 0 && end > start + 6).isTrue();
String version = msg.substring(start, end);
// System.err.println(version);
assertThat(GSON_VERSION_PATTERN.matcher(version).matches()).isTrue();
assertThat(version).matches(GSON_VERSION_PATTERN);
}
private static final class TestType {

View File

@ -17,6 +17,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@ -40,7 +41,7 @@ import org.junit.Before;
import org.junit.Test;
/**
* Functional tests for Json serialization and deserialization of classes with
* Functional tests for Json serialization and deserialization of classes with
* inheritance hierarchies.
*
* @author Inderjeet Singh
@ -85,7 +86,7 @@ public class InheritanceTest {
ClassWithBaseArrayField sub = new ClassWithBaseArrayField(baseClasses);
JsonObject json = gson.toJsonTree(sub).getAsJsonObject();
JsonArray bases = json.get(ClassWithBaseArrayField.FIELD_KEY).getAsJsonArray();
for (JsonElement element : bases) {
for (JsonElement element : bases) {
assertThat(element.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString()).isEqualTo(Sub.SUB_NAME);
}
}
@ -98,7 +99,7 @@ public class InheritanceTest {
ClassWithBaseCollectionField sub = new ClassWithBaseCollectionField(baseClasses);
JsonObject json = gson.toJsonTree(sub).getAsJsonObject();
JsonArray bases = json.get(ClassWithBaseArrayField.FIELD_KEY).getAsJsonArray();
for (JsonElement element : bases) {
for (JsonElement element : bases) {
assertThat(element.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString()).isEqualTo(Sub.SUB_NAME);
}
}
@ -162,6 +163,7 @@ public class InheritanceTest {
}
@Test
@SuppressWarnings("JdkObsolete")
public void testSubInterfacesOfCollectionSerialization() {
List<Integer> list = new LinkedList<>();
list.add(0);
@ -193,14 +195,14 @@ public class InheritanceTest {
String json = "{\"list\":[0,1,2,3],\"queue\":[0,1,2,3],\"set\":[0.1,0.2,0.3,0.4],"
+ "\"sortedSet\":[\"a\",\"b\",\"c\",\"d\"]"
+ "}";
ClassWithSubInterfacesOfCollection target =
ClassWithSubInterfacesOfCollection target =
gson.fromJson(json, ClassWithSubInterfacesOfCollection.class);
assertThat(target.listContains(0, 1, 2, 3)).isTrue();
assertThat(target.queueContains(0, 1, 2, 3)).isTrue();
assertThat(target.setContains(0.1F, 0.2F, 0.3F, 0.4F)).isTrue();
assertThat(target.sortedSetContains('a', 'b', 'c', 'd')).isTrue();
}
private static class ClassWithSubInterfacesOfCollection {
private List<Integer> list;
private Queue<Long> queue;
@ -223,7 +225,7 @@ public class InheritanceTest {
}
return true;
}
boolean queueContains(long... values) {
for (long value : values) {
if (!queue.contains(value)) {
@ -232,7 +234,7 @@ public class InheritanceTest {
}
return true;
}
boolean setContains(float... values) {
for (float value : values) {
if (!set.contains(value)) {
@ -250,7 +252,7 @@ public class InheritanceTest {
}
return true;
}
public String getExpectedJson() {
StringBuilder sb = new StringBuilder();
sb.append("{");
@ -266,6 +268,7 @@ public class InheritanceTest {
return sb.toString();
}
@CanIgnoreReturnValue
private StringBuilder append(StringBuilder sb, Collection<?> c) {
sb.append("[");
boolean first = true;

View File

@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.Strictness;
import com.google.gson.common.TestTypes.Base;
import com.google.gson.common.TestTypes.ClassWithBaseField;
import com.google.gson.common.TestTypes.Sub;
@ -33,7 +34,7 @@ import java.util.TreeSet;
import org.junit.Test;
/**
* Functional Test exercising custom serialization only. When test applies to both
* Functional Test exercising custom deserialization only. When test applies to both
* serialization and deserialization then add it to CustomTypeAdapterTest.
*
* @author Inderjeet Singh
@ -43,7 +44,7 @@ public class InstanceCreatorTest {
@Test
public void testInstanceCreatorReturnsBaseType() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (InstanceCreator<Base>) type -> new Base())
.create();
String json = "{baseName:'BaseRevised',subName:'Sub'}";
@ -54,7 +55,7 @@ public class InstanceCreatorTest {
@Test
public void testInstanceCreatorReturnsSubTypeForTopLevelObject() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (InstanceCreator<Base>) type -> new Sub())
.create();
@ -70,7 +71,7 @@ public class InstanceCreatorTest {
@Test
public void testInstanceCreatorReturnsSubTypeForField() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Base.class, (InstanceCreator<Base>) type -> new Sub())
.create();
String json = "{base:{baseName:'Base',subName:'SubRevised'}}";

View File

@ -19,28 +19,21 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.common.base.Splitter;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Locale;
import org.junit.Test;
/**
* Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on classes.
* Functional tests for the {@link JsonAdapter} annotation on classes.
*/
public final class JsonAdapterAnnotationOnClassesTest {
@ -101,7 +94,7 @@ public final class JsonAdapterAnnotationOnClassesTest {
}
};
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(A.class, serializer)
.create();
String json = gson.toJson(new A("abcd"));
@ -122,7 +115,7 @@ public final class JsonAdapterAnnotationOnClassesTest {
}
};
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(A.class, deserializer)
.create();
String json = gson.toJson(new A("abcd"));
@ -146,10 +139,84 @@ public final class JsonAdapterAnnotationOnClassesTest {
}
@Test
public void testNullSafeObjectFromJson() {
public void testNullSafeObject() {
Gson gson = new Gson();
NullableClass fromJson = gson.fromJson("null", NullableClass.class);
assertThat(fromJson).isNull();
fromJson = gson.fromJson("\"ignored\"", NullableClass.class);
assertThat(fromJson).isNotNull();
String json = gson.toJson(null, NullableClass.class);
assertThat(json).isEqualTo("null");
json = gson.toJson(new NullableClass());
assertThat(json).isEqualTo("\"nullable\"");
}
/**
* Tests behavior when a {@link TypeAdapterFactory} registered with {@code @JsonAdapter} returns
* {@code null}, indicating that it cannot handle the type and Gson should try a different factory
* instead.
*/
@Test
public void testFactoryReturningNull() {
Gson gson = new Gson();
assertThat(gson.fromJson("null", WithNullReturningFactory.class)).isNull();
assertThat(gson.toJson(null, WithNullReturningFactory.class)).isEqualTo("null");
TypeToken<WithNullReturningFactory<String>> stringTypeArg = new TypeToken<WithNullReturningFactory<String>>() {};
WithNullReturningFactory<?> deserialized = gson.fromJson("\"a\"", stringTypeArg);
assertThat(deserialized.t).isEqualTo("custom-read:a");
assertThat(gson.fromJson("null", stringTypeArg)).isNull();
assertThat(gson.toJson(new WithNullReturningFactory<>("b"), stringTypeArg.getType())).isEqualTo("\"custom-write:b\"");
assertThat(gson.toJson(null, stringTypeArg.getType())).isEqualTo("null");
// Factory should return `null` for this type and Gson should fall back to reflection-based adapter
TypeToken<WithNullReturningFactory<Integer>> numberTypeArg = new TypeToken<WithNullReturningFactory<Integer>>() {};
deserialized = gson.fromJson("{\"t\":1}", numberTypeArg);
assertThat(deserialized.t).isEqualTo(1);
assertThat(gson.toJson(new WithNullReturningFactory<>(2), numberTypeArg.getType())).isEqualTo("{\"t\":2}");
}
// Also set `nullSafe = true` to verify that this does not cause a NullPointerException if the
// factory would accidentally call `nullSafe()` on null adapter
@JsonAdapter(value = WithNullReturningFactory.NullReturningFactory.class, nullSafe = true)
private static class WithNullReturningFactory<T> {
T t;
public WithNullReturningFactory(T t) {
this.t = t;
}
static class NullReturningFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Don't handle raw (non-parameterized) type
if (type.getType() instanceof Class) {
return null;
}
ParameterizedType parameterizedType = (ParameterizedType) type.getType();
// Makes this test a bit more realistic by only conditionally returning null (instead of always)
if (parameterizedType.getActualTypeArguments()[0] != String.class) {
return null;
}
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<WithNullReturningFactory<String>>() {
@Override
public void write(JsonWriter out, WithNullReturningFactory<String> value) throws IOException {
out.value("custom-write:" + value.t);
}
@Override
public WithNullReturningFactory<String> read(JsonReader in) throws IOException {
return new WithNullReturningFactory<>("custom-read:" + in.nextString());
}
};
return adapter;
}
}
}
@JsonAdapter(A.JsonAdapter.class)
@ -222,18 +289,18 @@ public final class JsonAdapterAnnotationOnClassesTest {
out.name("name");
out.value(user.firstName + " " + user.lastName);
out.endObject();
// implement the write method
}
@Override public User read(JsonReader in) throws IOException {
// implement read: split name into firstName and lastName
in.beginObject();
in.nextName();
String[] nameParts = in.nextString().split(" ");
List<String> nameParts = Splitter.on(" ").splitToList(in.nextString());
in.endObject();
return new User(nameParts[0], nameParts[1]);
return new User(nameParts.get(0), nameParts.get(1));
}
}
// Implicit `nullSafe=true`
@JsonAdapter(value = NullableClassJsonAdapter.class)
private static class NullableClass {
}
@ -274,4 +341,396 @@ public final class JsonAdapterAnnotationOnClassesTest {
private static final class D {
@SuppressWarnings("unused") final String value = "a";
}
/**
* Verifies that {@link TypeAdapterFactory} specified by {@code @JsonAdapter} can
* call {@link Gson#getDelegateAdapter} without any issues, despite the factory
* not being directly registered on Gson.
*/
@Test
public void testDelegatingAdapterFactory() {
@SuppressWarnings("unchecked")
WithDelegatingFactory<String> deserialized = new Gson().fromJson("{\"custom\":{\"f\":\"de\"}}", WithDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("de");
deserialized = new Gson().fromJson("{\"custom\":{\"f\":\"de\"}}", new TypeToken<WithDelegatingFactory<String>>() {});
assertThat(deserialized.f).isEqualTo("de");
WithDelegatingFactory<String> serialized = new WithDelegatingFactory<>("se");
assertThat(new Gson().toJson(serialized)).isEqualTo("{\"custom\":{\"f\":\"se\"}}");
}
@JsonAdapter(WithDelegatingFactory.Factory.class)
private static class WithDelegatingFactory<T> {
T f;
WithDelegatingFactory(T f) {
this.f = f;
}
static class Factory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType() != WithDelegatingFactory.class) {
return null;
}
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
@Override
public T read(JsonReader in) throws IOException {
// Perform custom deserialization
in.beginObject();
assertThat(in.nextName()).isEqualTo("custom");
T t = delegate.read(in);
in.endObject();
return t;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
// Perform custom serialization
out.beginObject();
out.name("custom");
delegate.write(out, value);
out.endObject();
}
};
}
}
}
/**
* Similar to {@link #testDelegatingAdapterFactory}, except that the delegate is not
* looked up in {@code create} but instead in the adapter methods.
*/
@Test
public void testDelegatingAdapterFactory_Delayed() {
WithDelayedDelegatingFactory deserialized = new Gson().fromJson("{\"custom\":{\"f\":\"de\"}}", WithDelayedDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("de");
WithDelayedDelegatingFactory serialized = new WithDelayedDelegatingFactory("se");
assertThat(new Gson().toJson(serialized)).isEqualTo("{\"custom\":{\"f\":\"se\"}}");
}
@JsonAdapter(WithDelayedDelegatingFactory.Factory.class)
private static class WithDelayedDelegatingFactory {
String f;
WithDelayedDelegatingFactory(String f) {
this.f = f;
}
static class Factory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return new TypeAdapter<T>() {
@SuppressWarnings("SameNameButDifferent") // suppress Error Prone warning; should be clear that `Factory` refers to enclosing class
private TypeAdapter<T> delegate() {
return gson.getDelegateAdapter(Factory.this, type);
}
@Override
public T read(JsonReader in) throws IOException {
// Perform custom deserialization
in.beginObject();
assertThat(in.nextName()).isEqualTo("custom");
T t = delegate().read(in);
in.endObject();
return t;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
// Perform custom serialization
out.beginObject();
out.name("custom");
delegate().write(out, value);
out.endObject();
}
};
}
}
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when <i>different</i> instances of the same
* factory class are used; one registered on the {@code GsonBuilder} and the other implicitly
* through {@code @JsonAdapter}.
*/
@Test
public void testDelegating_SameFactoryClass() {
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new WithDelegatingFactory.Factory())
.create();
// Should use both factories, and therefore have `{"custom": ... }` twice
WithDelegatingFactory<?> deserialized = gson.fromJson("{\"custom\":{\"custom\":{\"f\":\"de\"}}}", WithDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("de");
WithDelegatingFactory<String> serialized = new WithDelegatingFactory<>("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"custom\":{\"f\":\"se\"}}}");
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when the <i>same</i> instance of a factory
* is used (through {@link InstanceCreator}).
*
* <p><b>Important:</b> This situation is likely a rare corner case; the purpose of this test is
* to verify that Gson behaves reasonable, mainly that it does not cause a {@link StackOverflowError}
* due to infinite recursion. This test is not intended to dictate an expected behavior.
*/
@Test
public void testDelegating_SameFactoryInstance() {
WithDelegatingFactory.Factory factory = new WithDelegatingFactory.Factory();
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(factory)
// Always provides same instance for factory
.registerTypeAdapter(WithDelegatingFactory.Factory.class, (InstanceCreator<?>) type -> factory)
.create();
// Current Gson.getDelegateAdapter implementation cannot tell when call is related to @JsonAdapter
// or not, it can only work based on the `skipPast` factory, so if the same factory instance is used
// the one registered with `GsonBuilder.registerTypeAdapterFactory` actually skips past the @JsonAdapter
// one, so the JSON string is `{"custom": ...}` instead of `{"custom":{"custom":...}}`
WithDelegatingFactory<?> deserialized = gson.fromJson("{\"custom\":{\"f\":\"de\"}}", WithDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("de");
WithDelegatingFactory<String> serialized = new WithDelegatingFactory<>("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"f\":\"se\"}}");
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when <i>different</i> instances of the same
* factory class are used; one specified with {@code @JsonAdapter} on a class, and the other specified
* with {@code @JsonAdapter} on a field of that class.
*
* <p><b>Important:</b> This situation is likely a rare corner case; the purpose of this test is
* to verify that Gson behaves reasonable, mainly that it does not cause a {@link StackOverflowError}
* due to infinite recursion. This test is not intended to dictate an expected behavior.
*/
@Test
public void testDelegating_SameFactoryClass_OnClassAndField() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
return in.nextString() + "-str";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value + "-str");
}
})
.create();
// Should use both factories, and therefore have `{"custom": ... }` once for class and once for the field,
// and for field also properly delegate to custom String adapter defined above
WithDelegatingFactoryOnClassAndField deserialized = gson.fromJson("{\"custom\":{\"f\":{\"custom\":\"de\"}}}",
WithDelegatingFactoryOnClassAndField.class);
assertThat(deserialized.f).isEqualTo("de-str");
WithDelegatingFactoryOnClassAndField serialized = new WithDelegatingFactoryOnClassAndField("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"f\":{\"custom\":\"se-str\"}}}");
}
/**
* Tests behavior of {@link Gson#getDelegateAdapter} when the <i>same</i> instance of a factory
* is used (through {@link InstanceCreator}); specified with {@code @JsonAdapter} on a class,
* and also specified with {@code @JsonAdapter} on a field of that class.
*
* <p><b>Important:</b> This situation is likely a rare corner case; the purpose of this test is
* to verify that Gson behaves reasonable, mainly that it does not cause a {@link StackOverflowError}
* due to infinite recursion. This test is not intended to dictate an expected behavior.
*/
@Test
public void testDelegating_SameFactoryInstance_OnClassAndField() {
WithDelegatingFactoryOnClassAndField.Factory factory = new WithDelegatingFactoryOnClassAndField.Factory();
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
return in.nextString() + "-str";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value + "-str");
}
})
// Always provides same instance for factory
.registerTypeAdapter(WithDelegatingFactoryOnClassAndField.Factory.class, (InstanceCreator<?>) type -> factory)
.create();
// Because field type (`String`) differs from declaring class, JsonAdapterAnnotationTypeAdapterFactory does
// not confuse factories and this behaves as expected: Both the declaring class and the field each have
// `{"custom": ...}` and delegation for the field to the custom String adapter defined above works properly
WithDelegatingFactoryOnClassAndField deserialized = gson.fromJson("{\"custom\":{\"f\":{\"custom\":\"de\"}}}",
WithDelegatingFactoryOnClassAndField.class);
assertThat(deserialized.f).isEqualTo("de-str");
WithDelegatingFactoryOnClassAndField serialized = new WithDelegatingFactoryOnClassAndField("se");
assertThat(gson.toJson(serialized)).isEqualTo("{\"custom\":{\"f\":{\"custom\":\"se-str\"}}}");
}
// Same factory class specified on class and one of its fields
@JsonAdapter(WithDelegatingFactoryOnClassAndField.Factory.class)
private static class WithDelegatingFactoryOnClassAndField {
@SuppressWarnings("SameNameButDifferent") // suppress Error Prone warning; should be clear that `Factory` refers to nested class
@JsonAdapter(Factory.class)
String f;
WithDelegatingFactoryOnClassAndField(String f) {
this.f = f;
}
static class Factory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
@Override
public T read(JsonReader in) throws IOException {
// Perform custom deserialization
in.beginObject();
assertThat(in.nextName()).isEqualTo("custom");
T t = delegate.read(in);
in.endObject();
return t;
}
@Override
public void write(JsonWriter out, T value) throws IOException {
// Perform custom serialization
out.beginObject();
out.name("custom");
delegate.write(out, value);
out.endObject();
}
};
}
}
}
/**
* Tests usage of {@link JsonSerializer} as {@link JsonAdapter} value
*/
@Test
public void testJsonSerializer() {
Gson gson = new Gson();
// Verify that delegate deserializer (reflection deserializer) is used
WithJsonSerializer deserialized = gson.fromJson("{\"f\":\"test\"}", WithJsonSerializer.class);
assertThat(deserialized.f).isEqualTo("test");
String json = gson.toJson(new WithJsonSerializer());
// Uses custom serializer which always returns `true`
assertThat(json).isEqualTo("true");
}
@JsonAdapter(WithJsonSerializer.Serializer.class)
private static class WithJsonSerializer {
String f = "";
static class Serializer implements JsonSerializer<WithJsonSerializer> {
@Override
public JsonElement serialize(WithJsonSerializer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(true);
}
}
}
/**
* Tests usage of {@link JsonDeserializer} as {@link JsonAdapter} value
*/
@Test
public void testJsonDeserializer() {
Gson gson = new Gson();
WithJsonDeserializer deserialized = gson.fromJson("{\"f\":\"test\"}", WithJsonDeserializer.class);
// Uses custom deserializer which always uses "123" as field value
assertThat(deserialized.f).isEqualTo("123");
// Verify that delegate serializer (reflection serializer) is used
String json = gson.toJson(new WithJsonDeserializer("abc"));
assertThat(json).isEqualTo("{\"f\":\"abc\"}");
}
@JsonAdapter(WithJsonDeserializer.Deserializer.class)
private static class WithJsonDeserializer {
String f;
WithJsonDeserializer(String f) {
this.f = f;
}
static class Deserializer implements JsonDeserializer<WithJsonDeserializer> {
@Override
public WithJsonDeserializer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new WithJsonDeserializer("123");
}
}
}
/**
* Tests creation of the adapter referenced by {@code @JsonAdapter} using an {@link InstanceCreator}.
*/
@Test
public void testAdapterCreatedByInstanceCreator() {
CreatedByInstanceCreator.Serializer serializer = new CreatedByInstanceCreator.Serializer("custom");
Gson gson = new GsonBuilder()
.registerTypeAdapter(CreatedByInstanceCreator.Serializer.class, (InstanceCreator<?>) t -> serializer)
.create();
String json = gson.toJson(new CreatedByInstanceCreator());
assertThat(json).isEqualTo("\"custom\"");
}
@JsonAdapter(CreatedByInstanceCreator.Serializer.class)
private static class CreatedByInstanceCreator {
static class Serializer implements JsonSerializer<CreatedByInstanceCreator> {
private final String value;
@SuppressWarnings("unused")
public Serializer() {
throw new AssertionError("should not be called");
}
public Serializer(String value) {
this.value = value;
}
@Override
public JsonElement serialize(CreatedByInstanceCreator src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(value);
}
}
}
/**
* Tests creation of the adapter referenced by {@code @JsonAdapter} using JDK Unsafe.
*/
@Test
public void testAdapterCreatedByJdkUnsafe() {
String json = new Gson().toJson(new CreatedByJdkUnsafe());
assertThat(json).isEqualTo("false");
}
@JsonAdapter(CreatedByJdkUnsafe.Serializer.class)
private static class CreatedByJdkUnsafe {
static class Serializer implements JsonSerializer<CreatedByJdkUnsafe> {
// JDK Unsafe leaves this at default value `false`
private boolean wasInitialized = true;
// Explicit constructor with args to remove implicit no-args constructor
@SuppressWarnings("unused")
public Serializer(int i) {
throw new AssertionError("should not be called");
}
@Override
public JsonElement serialize(CreatedByJdkUnsafe src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(wasInitialized);
}
}
}
}

View File

@ -18,21 +18,21 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
/**
* Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on fields.
* Functional tests for the {@link JsonAdapter} annotation on fields.
*/
public final class JsonAdapterAnnotationOnFieldsTest {
@Test
@ -67,7 +67,7 @@ public final class JsonAdapterAnnotationOnFieldsTest {
@Test
public void testFieldAnnotationTakesPrecedenceOverRegisteredTypeAdapter() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Part.class, new TypeAdapter<Part>() {
@Override public void write(JsonWriter out, Part part) {
throw new AssertionError();
@ -119,7 +119,7 @@ public final class JsonAdapterAnnotationOnFieldsTest {
out.value("PartJsonFieldAnnotationAdapter");
}
@Override public Part read(JsonReader in) throws IOException {
in.nextString();
String unused = in.nextString();
return new Part("PartJsonFieldAnnotationAdapter");
}
}
@ -132,7 +132,7 @@ public final class JsonAdapterAnnotationOnFieldsTest {
}
@SuppressWarnings("unchecked")
@Override public T read(JsonReader in) throws IOException {
in.nextString();
String unused = in.nextString();
return (T) new Part("GizmoPartTypeAdapterFactory");
}
};
@ -159,7 +159,7 @@ public final class JsonAdapterAnnotationOnFieldsTest {
out.value("UserClassAnnotationAdapter");
}
@Override public User read(JsonReader in) throws IOException {
in.nextString();
String unused = in.nextString();
return new User("UserClassAnnotationAdapter");
}
}
@ -178,7 +178,7 @@ public final class JsonAdapterAnnotationOnFieldsTest {
out.value("UserFieldAnnotationAdapter");
}
@Override public User read(JsonReader in) throws IOException {
in.nextString();
String unused = in.nextString();
return new User("UserFieldAnnotationAdapter");
}
}
@ -188,7 +188,7 @@ public final class JsonAdapterAnnotationOnFieldsTest {
out.value("RegisteredUserAdapter");
}
@Override public User read(JsonReader in) throws IOException {
in.nextString();
String unused = in.nextString();
return new User("RegisteredUserAdapter");
}
}
@ -308,10 +308,345 @@ public final class JsonAdapterAnnotationOnFieldsTest {
}
@SuppressWarnings("unchecked")
@Override public T read(JsonReader in) throws IOException {
in.nextString();
String unused = in.nextString();
return (T) Arrays.asList(new Part("GizmoPartTypeAdapterFactory"));
}
};
}
}
/**
* Verify that {@link JsonAdapter} annotation can overwrite adapters which
* can normally not be overwritten (in this case adapter for {@link JsonElement}).
*/
@Test
public void testOverwriteBuiltIn() {
BuiltInOverwriting obj = new BuiltInOverwriting();
obj.f = new JsonPrimitive(true);
String json = new Gson().toJson(obj);
assertThat(json).isEqualTo("{\"f\":\"" + JsonElementAdapter.SERIALIZED + "\"}");
BuiltInOverwriting deserialized = new Gson().fromJson("{\"f\": 2}", BuiltInOverwriting.class);
assertThat(deserialized.f).isEqualTo(JsonElementAdapter.DESERIALIZED);
}
private static class BuiltInOverwriting {
@JsonAdapter(JsonElementAdapter.class)
JsonElement f;
}
private static class JsonElementAdapter extends TypeAdapter<JsonElement> {
static final JsonPrimitive DESERIALIZED = new JsonPrimitive("deserialized hardcoded");
@Override public JsonElement read(JsonReader in) throws IOException {
in.skipValue();
return DESERIALIZED;
}
static final String SERIALIZED = "serialized hardcoded";
@Override public void write(JsonWriter out, JsonElement value) throws IOException {
out.value(SERIALIZED);
}
}
/**
* Verify that exclusion strategy preventing serialization has higher precedence than
* {@link JsonAdapter} annotation.
*/
@Test
public void testExcludeSerializePrecedence() {
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override public boolean shouldSkipField(FieldAttributes f) {
return true;
}
@Override public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
DelegatingAndOverwriting obj = new DelegatingAndOverwriting();
obj.f = 1;
obj.f2 = new JsonPrimitive(2);
obj.f3 = new JsonPrimitive(true);
String json = gson.toJson(obj);
assertThat(json).isEqualTo("{}");
DelegatingAndOverwriting deserialized = gson.fromJson("{\"f\":1,\"f2\":2,\"f3\":3}", DelegatingAndOverwriting.class);
assertThat(deserialized.f).isEqualTo(Integer.valueOf(1));
assertThat(deserialized.f2).isEqualTo(new JsonPrimitive(2));
// Verify that for deserialization type adapter specified by @JsonAdapter is used
assertThat(deserialized.f3).isEqualTo(JsonElementAdapter.DESERIALIZED);
}
/**
* Verify that exclusion strategy preventing deserialization has higher precedence than
* {@link JsonAdapter} annotation.
*/
@Test
public void testExcludeDeserializePrecedence() {
Gson gson = new GsonBuilder()
.addDeserializationExclusionStrategy(new ExclusionStrategy() {
@Override public boolean shouldSkipField(FieldAttributes f) {
return true;
}
@Override public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
DelegatingAndOverwriting obj = new DelegatingAndOverwriting();
obj.f = 1;
obj.f2 = new JsonPrimitive(2);
obj.f3 = new JsonPrimitive(true);
String json = gson.toJson(obj);
// Verify that for serialization type adapters specified by @JsonAdapter are used
assertThat(json).isEqualTo("{\"f\":1,\"f2\":2,\"f3\":\"" + JsonElementAdapter.SERIALIZED + "\"}");
DelegatingAndOverwriting deserialized = gson.fromJson("{\"f\":1,\"f2\":2,\"f3\":3}", DelegatingAndOverwriting.class);
assertThat(deserialized.f).isNull();
assertThat(deserialized.f2).isNull();
assertThat(deserialized.f3).isNull();
}
/**
* Verify that exclusion strategy preventing serialization and deserialization has
* higher precedence than {@link JsonAdapter} annotation.
*
* <p>This is a separate test method because {@link ReflectiveTypeAdapterFactory} handles
* this case differently.
*/
@Test
public void testExcludePrecedence() {
Gson gson = new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
@Override public boolean shouldSkipField(FieldAttributes f) {
return true;
}
@Override public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
DelegatingAndOverwriting obj = new DelegatingAndOverwriting();
obj.f = 1;
obj.f2 = new JsonPrimitive(2);
obj.f3 = new JsonPrimitive(true);
String json = gson.toJson(obj);
assertThat(json).isEqualTo("{}");
DelegatingAndOverwriting deserialized = gson.fromJson("{\"f\":1,\"f2\":2,\"f3\":3}", DelegatingAndOverwriting.class);
assertThat(deserialized.f).isNull();
assertThat(deserialized.f2).isNull();
assertThat(deserialized.f3).isNull();
}
private static class DelegatingAndOverwriting {
@JsonAdapter(DelegatingAdapterFactory.class)
Integer f;
@JsonAdapter(DelegatingAdapterFactory.class)
JsonElement f2;
// Also have non-delegating adapter to make tests handle both cases
@JsonAdapter(JsonElementAdapter.class)
JsonElement f3;
static class DelegatingAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return gson.getDelegateAdapter(this, type);
}
}
}
/**
* Verifies that {@link TypeAdapterFactory} specified by {@code @JsonAdapter} can
* call {@link Gson#getDelegateAdapter} without any issues, despite the factory
* not being directly registered on Gson.
*/
@Test
public void testDelegatingAdapterFactory() {
@SuppressWarnings("unchecked")
WithDelegatingFactory<String> deserialized = new Gson().fromJson("{\"f\":\"test\"}", WithDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("test-custom");
deserialized = new Gson().fromJson("{\"f\":\"test\"}", new TypeToken<WithDelegatingFactory<String>>() {});
assertThat(deserialized.f).isEqualTo("test-custom");
WithDelegatingFactory<String> serialized = new WithDelegatingFactory<>();
serialized.f = "value";
assertThat(new Gson().toJson(serialized)).isEqualTo("{\"f\":\"value-custom\"}");
}
private static class WithDelegatingFactory<T> {
@SuppressWarnings("SameNameButDifferent") // suppress Error Prone warning; should be clear that `Factory` refers to nested class
@JsonAdapter(Factory.class)
T f;
static class Factory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<String> delegate = (TypeAdapter<String>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
// Perform custom deserialization
return delegate.read(in) + "-custom";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
// Perform custom serialization
delegate.write(out, value + "-custom");
}
};
}
}
}
/**
* Similar to {@link #testDelegatingAdapterFactory}, except that the delegate is not
* looked up in {@code create} but instead in the adapter methods.
*/
@Test
public void testDelegatingAdapterFactory_Delayed() {
WithDelayedDelegatingFactory deserialized = new Gson().fromJson("{\"f\":\"test\"}", WithDelayedDelegatingFactory.class);
assertThat(deserialized.f).isEqualTo("test-custom");
WithDelayedDelegatingFactory serialized = new WithDelayedDelegatingFactory();
serialized.f = "value";
assertThat(new Gson().toJson(serialized)).isEqualTo("{\"f\":\"value-custom\"}");
}
@SuppressWarnings("SameNameButDifferent") // suppress Error Prone warning; should be clear that `Factory` refers to nested class
private static class WithDelayedDelegatingFactory {
@JsonAdapter(Factory.class)
String f;
static class Factory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (TypeAdapter<T>) new TypeAdapter<String>() {
private TypeAdapter<String> delegate() {
return (TypeAdapter<String>) gson.getDelegateAdapter(Factory.this, type);
}
@Override
public String read(JsonReader in) throws IOException {
// Perform custom deserialization
return delegate().read(in) + "-custom";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
// Perform custom serialization
delegate().write(out, value + "-custom");
}
};
}
}
}
/**
* Tests usage of {@link Gson#getAdapter(TypeToken)} in the {@code create} method of the factory.
* Existing code was using that as workaround because {@link Gson#getDelegateAdapter} previously
* did not work in combination with {@code @JsonAdapter}, see https://github.com/google/gson/issues/1028.
*/
@Test
public void testGetAdapterDelegation() {
Gson gson = new Gson();
GetAdapterDelegation deserialized = gson.fromJson("{\"f\":\"de\"}", GetAdapterDelegation.class);
assertThat(deserialized.f).isEqualTo("de-custom");
String json = gson.toJson(new GetAdapterDelegation("se"));
assertThat(json).isEqualTo("{\"f\":\"se-custom\"}");
}
private static class GetAdapterDelegation {
@SuppressWarnings("SameNameButDifferent") // suppress Error Prone warning; should be clear that `Factory` refers to nested class
@JsonAdapter(Factory.class)
String f;
GetAdapterDelegation(String f) {
this.f = f;
}
static class Factory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Uses `Gson.getAdapter` instead of `Gson.getDelegateAdapter`
TypeAdapter<String> delegate = (TypeAdapter<String>) gson.getAdapter(type);
return (TypeAdapter<T>) new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
return delegate.read(in) + "-custom";
}
@Override
public void write(JsonWriter out, String value) throws IOException {
delegate.write(out, value + "-custom");
}
};
}
}
}
/**
* Tests usage of {@link JsonSerializer} as {@link JsonAdapter} value on a field
*/
@Test
public void testJsonSerializer() {
Gson gson = new Gson();
// Verify that delegate deserializer for List is used
WithJsonSerializer deserialized = gson.fromJson("{\"f\":[1,2,3]}", WithJsonSerializer.class);
assertThat(deserialized.f).isEqualTo(Arrays.asList(1, 2, 3));
String json = gson.toJson(new WithJsonSerializer());
// Uses custom serializer which always returns `true`
assertThat(json).isEqualTo("{\"f\":true}");
}
private static class WithJsonSerializer {
@JsonAdapter(Serializer.class)
List<Integer> f = Collections.emptyList();
static class Serializer implements JsonSerializer<List<Integer>> {
@Override
public JsonElement serialize(List<Integer> src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(true);
}
}
}
/**
* Tests usage of {@link JsonDeserializer} as {@link JsonAdapter} value on a field
*/
@Test
public void testJsonDeserializer() {
Gson gson = new Gson();
WithJsonDeserializer deserialized = gson.fromJson("{\"f\":[5]}", WithJsonDeserializer.class);
// Uses custom deserializer which always returns `[3, 2, 1]`
assertThat(deserialized.f).isEqualTo(Arrays.asList(3, 2, 1));
// Verify that delegate serializer for List is used
String json = gson.toJson(new WithJsonDeserializer(Arrays.asList(4, 5, 6)));
assertThat(json).isEqualTo("{\"f\":[4,5,6]}");
}
private static class WithJsonDeserializer {
@JsonAdapter(Deserializer.class)
List<Integer> f;
WithJsonDeserializer(List<Integer> f) {
this.f = f;
}
static class Deserializer implements JsonDeserializer<List<Integer>> {
@Override
public List<Integer> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return Arrays.asList(3, 2, 1);
}
}
}
}

View File

@ -18,7 +18,9 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.errorprone.annotations.Keep;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@ -26,7 +28,11 @@ import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import org.junit.Test;
@ -42,14 +48,20 @@ public final class JsonAdapterSerializerDeserializerTest {
String json = gson.toJson(new Computer(new User("Inderjeet Singh"), null, new User("Jesse Wilson")));
assertThat(json).isEqualTo("{\"user1\":\"UserSerializer\",\"user3\":\"UserSerializerDeserializer\"}");
Computer computer = gson.fromJson("{'user2':'Jesse Wilson','user3':'Jake Wharton'}", Computer.class);
assertThat(computer.user2.name).isEqualTo("UserSerializer");
assertThat(computer.user2.name).isEqualTo("UserDeserializer");
assertThat(computer.user3.name).isEqualTo("UserSerializerDeserializer");
}
private static final class Computer {
@JsonAdapter(UserSerializer.class) final User user1;
@JsonAdapter(UserDeserializer.class) final User user2;
@JsonAdapter(UserSerializerDeserializer.class) final User user3;
@JsonAdapter(UserSerializer.class)
@Keep
final User user1;
@JsonAdapter(UserDeserializer.class)
@Keep
final User user2;
@JsonAdapter(UserSerializerDeserializer.class)
@Keep
final User user3;
Computer(User user1, User user2, User user3) {
this.user1 = user1;
this.user2 = user2;
@ -75,7 +87,7 @@ public final class JsonAdapterSerializerDeserializerTest {
@Override
public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new User("UserSerializer");
return new User("UserDeserializer");
}
}
@ -137,8 +149,12 @@ public final class JsonAdapterSerializerDeserializerTest {
}
private static final class Container {
@JsonAdapter(BaseStringAdapter.class) Base<String> a;
@JsonAdapter(BaseIntegerAdapter.class) Base<Integer> b;
@JsonAdapter(BaseStringAdapter.class)
@Keep
Base<String> a;
@JsonAdapter(BaseIntegerAdapter.class)
@Keep
Base<Integer> b;
Container(String a, int b) {
this.a = new Base<>(a);
this.b = new Base<>(b);
@ -167,20 +183,48 @@ public final class JsonAdapterSerializerDeserializerTest {
@Test
public void testJsonAdapterNullSafe() {
Gson gson = new Gson();
String json = gson.toJson(new Computer3(null, null));
assertThat(json).isEqualTo("{\"user1\":\"UserSerializerDeserializer\"}");
Computer3 computer3 = gson.fromJson("{\"user1\":null, \"user2\":null}", Computer3.class);
assertThat(computer3.user1.name).isEqualTo("UserSerializerDeserializer");
assertThat(computer3.user2).isNull();
Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new TypeAdapter<User>() {
@Override
public User read(JsonReader in) throws IOException {
in.nextNull();
return new User("fallback-read");
}
@Override
public void write(JsonWriter out, User value) throws IOException {
assertThat(value).isNull();
out.value("fallback-write");
}
})
.serializeNulls()
.create();
String json = gson.toJson(new WithNullSafe(null, null, null, null));
// Only nullSafe=true serializer writes null; for @JsonAdapter with deserializer nullSafe is ignored when serializing
assertThat(json).isEqualTo("{\"userS\":\"UserSerializer\",\"userSN\":null,\"userD\":\"fallback-write\",\"userDN\":\"fallback-write\"}");
WithNullSafe deserialized = gson.fromJson("{\"userS\":null,\"userSN\":null,\"userD\":null,\"userDN\":null}", WithNullSafe.class);
// For @JsonAdapter with serializer nullSafe is ignored when deserializing
assertThat(deserialized.userS.name).isEqualTo("fallback-read");
assertThat(deserialized.userSN.name).isEqualTo("fallback-read");
assertThat(deserialized.userD.name).isEqualTo("UserDeserializer");
assertThat(deserialized.userDN).isNull();
}
private static final class Computer3 {
@JsonAdapter(value = UserSerializerDeserializer.class, nullSafe = false) final User user1;
@JsonAdapter(value = UserSerializerDeserializer.class) final User user2;
Computer3(User user1, User user2) {
this.user1 = user1;
this.user2 = user2;
private static final class WithNullSafe {
// "userS..." uses JsonSerializer
@JsonAdapter(value = UserSerializer.class, nullSafe = false) final User userS;
@JsonAdapter(value = UserSerializer.class, nullSafe = true) final User userSN;
// "userD..." uses JsonDeserializer
@JsonAdapter(value = UserDeserializer.class, nullSafe = false) final User userD;
@JsonAdapter(value = UserDeserializer.class, nullSafe = true) final User userDN;
WithNullSafe(User userS, User userSN, User userD, User userDN) {
this.userS = userS;
this.userSN = userSN;
this.userD = userD;
this.userDN = userDN;
}
}
}

View File

@ -32,6 +32,7 @@ public class LeniencyTest {
private Gson gson;
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
@Before
public void setUp() throws Exception {
gson = new Gson();

View File

@ -22,6 +22,7 @@ import static org.junit.Assert.fail;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.Strictness;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
@ -59,7 +60,7 @@ public class MapAsArrayTypeAdapterTest {
@Test
@Ignore
public void disabled_testTwoTypesCollapseToOneSerialize() {
public void testTwoTypesCollapseToOneSerialize() {
Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
.create();
@ -116,7 +117,7 @@ public class MapAsArrayTypeAdapterTest {
@Test
public void testMapWithTypeVariableDeserialization() {
Gson gson = new GsonBuilder().setLenient().enableComplexMapKeySerialization().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).enableComplexMapKeySerialization().create();
String json = "{map:[[{x:2,y:3},{x:4,y:5}]]}";
Type type = new TypeToken<PointWithProperty<Point>>(){}.getType();
PointWithProperty<Point> map = gson.fromJson(json, type);

View File

@ -17,7 +17,6 @@
package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@ -57,8 +56,8 @@ public class MoreSpecificTypeSerializationTest {
list.add(new Sub(2, 3));
ClassWithContainersOfBaseFields target = new ClassWithContainersOfBaseFields(list, null);
String json = gson.toJson(target);
assertWithMessage(json).that(json).contains("{\"b\":1}");
assertWithMessage(json).that(json).contains("{\"s\":3,\"b\":2}");
assertThat(json).contains("{\"b\":1}");
assertThat(json).contains("{\"s\":3,\"b\":2}");
}
@Test
@ -98,8 +97,8 @@ public class MoreSpecificTypeSerializationTest {
ClassWithContainersOfParameterizedBaseFields target =
new ClassWithContainersOfParameterizedBaseFields(list, null);
String json = gson.toJson(target);
assertWithMessage(json).that(json).contains("{\"t\":\"one\"}");
assertWithMessage(json).that(json).doesNotContain("\"s\":");
assertThat(json).contains("{\"t\":\"one\"}");
assertThat(json).doesNotContain("\"s\":");
}
/**

View File

@ -26,6 +26,7 @@ import com.google.gson.annotations.SerializedName;
import com.google.gson.common.TestTypes.ClassWithSerializedNameFields;
import com.google.gson.common.TestTypes.StringWrapper;
import java.lang.reflect.Field;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
@ -137,7 +138,29 @@ public class NamingPolicyTest {
assertThat(expected).hasMessageThat()
.isEqualTo("Class com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields declares multiple JSON fields named 'a';"
+ " conflict is caused by fields com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#a and"
+ " com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#b");
+ " com.google.gson.functional.NamingPolicyTest$ClassWithDuplicateFields#b"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#duplicate-fields");
}
}
@Test
public void testGsonDuplicateNameDueToBadNamingPolicy() {
Gson gson = builder.setFieldNamingStrategy(new FieldNamingStrategy() {
@Override
public String translateName(Field f) {
return "x";
}
}).create();
try {
gson.toJson(new ClassWithTwoFields());
fail();
} catch (IllegalArgumentException expected) {
assertThat(expected).hasMessageThat()
.isEqualTo("Class com.google.gson.functional.NamingPolicyTest$ClassWithTwoFields declares multiple JSON fields named 'x';"
+ " conflict is caused by fields com.google.gson.functional.NamingPolicyTest$ClassWithTwoFields#a and"
+ " com.google.gson.functional.NamingPolicyTest$ClassWithTwoFields#b"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#duplicate-fields");
}
}
@ -209,7 +232,7 @@ public class NamingPolicyTest {
private static final class UpperCaseNamingStrategy implements FieldNamingStrategy {
@Override
public String translateName(Field f) {
return f.getName().toUpperCase();
return f.getName().toUpperCase(Locale.ROOT);
}
}
@ -239,4 +262,12 @@ public class NamingPolicyTest {
this.value = value;
}
}
@SuppressWarnings("unused")
private static class ClassWithTwoFields {
public int a;
public int b;
public ClassWithTwoFields() {}
}
}

View File

@ -18,15 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.*;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassWithObjects;
import java.lang.reflect.Type;
@ -240,7 +232,7 @@ public class NullObjectAndFieldTest {
@Test
public void testCustomTypeAdapterPassesNullDesrialization() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(ObjectWithField.class, (JsonDeserializer<ObjectWithField>) (json, type, context) -> context.deserialize(null, type)).create();
String json = "{value:'value1'}";
ObjectWithField target = gson.fromJson(json, ObjectWithField.class);

View File

@ -161,7 +161,7 @@ public class ObjectTest {
private static class Subclass extends Superclass1 {
}
private static class Superclass1 extends Superclass2 {
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "HidingField"})
String s;
}
private static class Superclass2 {
@ -177,7 +177,8 @@ public class ObjectTest {
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().isEqualTo("Class com.google.gson.functional.ObjectTest$Subclass declares multiple JSON fields named 's';"
+ " conflict is caused by fields com.google.gson.functional.ObjectTest$Superclass1#s and"
+ " com.google.gson.functional.ObjectTest$Superclass2#s");
+ " com.google.gson.functional.ObjectTest$Superclass2#s"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#duplicate-fields");
}
}
@ -196,6 +197,7 @@ public class ObjectTest {
Nested target = gson.fromJson(json, Nested.class);
assertThat(target.getExpectedJson()).isEqualTo(json);
}
@Test
public void testNullSerialization() {
assertThat(gson.toJson(null)).isEqualTo("null");
@ -414,6 +416,8 @@ public class ObjectTest {
private static class Parent {
@SuppressWarnings("unused")
int value1 = 1;
@SuppressWarnings("ClassCanBeStatic")
private class Child {
int value2 = 2;
}
@ -537,17 +541,17 @@ public class ObjectTest {
Gson gson = new Gson();
Product product = new Product();
assertThat(gson.toJson(product)).isEqualTo("{\"attributes\":[],\"departments\":[]}");
gson.fromJson(gson.toJson(product), Product.class);
Product unused1 = gson.fromJson(gson.toJson(product), Product.class);
product.departments.add(new Department());
assertThat(gson.toJson(product))
.isEqualTo("{\"attributes\":[],\"departments\":[{\"name\":\"abc\",\"code\":\"123\"}]}");
gson.fromJson(gson.toJson(product), Product.class);
Product unused2 = gson.fromJson(gson.toJson(product), Product.class);
product.attributes.add("456");
assertThat(gson.toJson(product))
.isEqualTo("{\"attributes\":[\"456\"],\"departments\":[{\"name\":\"abc\",\"code\":\"123\"}]}");
gson.fromJson(gson.toJson(product), Product.class);
Product unused3 = gson.fromJson(gson.toJson(product), Product.class);
}
static final class Department {
@ -562,14 +566,12 @@ public class ObjectTest {
// http://code.google.com/p/google-gson/issues/detail?id=270
@Test
@SuppressWarnings("JavaUtilDate")
public void testDateAsMapObjectField() {
HasObjectMap a = new HasObjectMap();
a.map.put("date", new Date(0));
if (JavaVersion.isJava9OrLater()) {
assertThat(gson.toJson(a)).isEqualTo("{\"map\":{\"date\":\"Dec 31, 1969, 4:00:00 PM\"}}");
} else {
assertThat(gson.toJson(a)).isEqualTo("{\"map\":{\"date\":\"Dec 31, 1969 4:00:00 PM\"}}");
}
assertThat(gson.toJson(a))
.matches("\\{\"map\":\\{\"date\":\"Dec 31, 1969,? 4:00:00\\hPM\"\\}\\}");
}
static class HasObjectMap {
@ -608,7 +610,7 @@ public class ObjectTest {
@Test
public void testStaticFieldDeserialization() {
// By default Gson should ignore static fields
gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticField.class);
ClassWithStaticField unused = gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticField.class);
assertThat(ClassWithStaticField.s).isEqualTo("initial");
Gson gson = new GsonBuilder()
@ -629,7 +631,7 @@ public class ObjectTest {
gson.fromJson("{\"s\":\"custom\"}", ClassWithStaticFinalField.class);
fail();
} catch (JsonIOException e) {
assertThat( e.getMessage()).isEqualTo("Cannot set value of 'static final' field 'com.google.gson.functional.ObjectTest$ClassWithStaticFinalField#s'");
assertThat(e).hasMessageThat().isEqualTo("Cannot set value of 'static final' field 'com.google.gson.functional.ObjectTest$ClassWithStaticFinalField#s'");
}
}
@ -649,11 +651,12 @@ public class ObjectTest {
}
// TODO: Adjust this once Gson throws more specific exception type
catch (RuntimeException e) {
assertThat( e.getMessage()).isEqualTo("Failed to invoke constructor 'com.google.gson.functional.ObjectTest$ClassWithThrowingConstructor()' with no args");
assertThat(e).hasMessageThat().isEqualTo("Failed to invoke constructor 'com.google.gson.functional.ObjectTest$ClassWithThrowingConstructor()' with no args");
assertThat(e).hasCauseThat().isSameInstanceAs(ClassWithThrowingConstructor.thrownException);
}
}
@SuppressWarnings("StaticAssignmentOfThrowable")
static class ClassWithThrowingConstructor {
static final RuntimeException thrownException = new RuntimeException("Custom exception");

View File

@ -18,6 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.base.Objects;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
@ -399,7 +400,7 @@ public class ParameterizedTypesTest {
}
}
private static class MultiParameters<A, B, C, D, E> {
private static final class MultiParameters<A, B, C, D, E> {
A a;
B b;
C c;
@ -429,54 +430,19 @@ public class ParameterizedTypesTest {
return result;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
if (this == obj) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (obj == null) {
if (!(o instanceof MultiParameters<?, ?, ?, ?, ?>)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MultiParameters<A, B, C, D, E> other = (MultiParameters<A, B, C, D, E>) obj;
if (a == null) {
if (other.a != null) {
return false;
}
} else if (!a.equals(other.a)) {
return false;
}
if (b == null) {
if (other.b != null) {
return false;
}
} else if (!b.equals(other.b)) {
return false;
}
if (c == null) {
if (other.c != null) {
return false;
}
} else if (!c.equals(other.c)) {
return false;
}
if (d == null) {
if (other.d != null) {
return false;
}
} else if (!d.equals(other.d)) {
return false;
}
if (e == null) {
if (other.e != null) {
return false;
}
} else if (!e.equals(other.e)) {
return false;
}
return true;
MultiParameters<?, ?, ?, ?, ?> that = (MultiParameters<?, ?, ?, ?, ?>) o;
return Objects.equal(a, that.a)
&& Objects.equal(b, that.b)
&& Objects.equal(c, that.c)
&& Objects.equal(d, that.d)
&& Objects.equal(e, that.e);
}
}

View File

@ -23,9 +23,9 @@ import com.google.gson.common.TestTypes.ArrayOfObjects;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.junit.Before;
@ -51,7 +51,7 @@ public class PrettyPrintingTest {
@Test
public void testPrettyPrintList() {
BagOfPrimitives b = new BagOfPrimitives();
List<BagOfPrimitives> listOfB = new LinkedList<>();
List<BagOfPrimitives> listOfB = new ArrayList<>();
for (int i = 0; i < 15; ++i) {
listOfB.add(b);
}
@ -91,7 +91,7 @@ public class PrettyPrintingTest {
assertThat(json).isEqualTo("[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ],\n [\n 5,\n 6\n ],"
+ "\n [\n 7,\n 8\n ],\n [\n 9,\n 0\n ],\n [\n 10\n ]\n]");
}
@Test
public void testMap() {
Map<String, Integer> map = new LinkedHashMap<>();

View File

@ -88,21 +88,21 @@ public class PrimitiveTest {
gson.fromJson("-129", byte.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("Lossy conversion from -129 to byte; at path $");
assertThat(e).hasMessageThat().isEqualTo("Lossy conversion from -129 to byte; at path $");
}
try {
gson.fromJson("256", byte.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("Lossy conversion from 256 to byte; at path $");
assertThat(e).hasMessageThat().isEqualTo("Lossy conversion from 256 to byte; at path $");
}
try {
gson.fromJson("2147483648", byte.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 (char '\0') path $");
assertThat(e).hasMessageThat().isEqualTo("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 (char '\0') path $");
}
}
@ -136,21 +136,21 @@ public class PrimitiveTest {
gson.fromJson("-32769", short.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("Lossy conversion from -32769 to short; at path $");
assertThat(e).hasMessageThat().isEqualTo("Lossy conversion from -32769 to short; at path $");
}
try {
gson.fromJson("65536", short.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("Lossy conversion from 65536 to short; at path $");
assertThat(e).hasMessageThat().isEqualTo("Lossy conversion from 65536 to short; at path $");
}
try {
gson.fromJson("2147483648", short.class);
fail();
} catch (JsonSyntaxException e) {
assertThat(e.getMessage()).isEqualTo("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 (char '\0') path $");
assertThat(e).hasMessageThat().isEqualTo("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 (char '\0') path $");
}
}
@ -188,7 +188,7 @@ public class PrimitiveTest {
// Should perform widening conversion
assertThat(gson.toJson((byte) 1, Float.class)).isEqualTo("1.0");
// (This widening conversion is actually lossy)
assertThat(gson.toJson(Long.MAX_VALUE - 10L, Float.class)).isEqualTo(Float.toString(Long.MAX_VALUE - 10L));
assertThat(gson.toJson(Long.MAX_VALUE - 10L, Float.class)).isEqualTo(Float.toString((float) (Long.MAX_VALUE - 10L)));
// Should perform narrowing conversion
gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
assertThat(gson.toJson(Double.MAX_VALUE, Float.class)).isEqualTo("Infinity");
@ -203,7 +203,7 @@ public class PrimitiveTest {
// Should perform widening conversion
assertThat(gson.toJson((byte) 1, Double.class)).isEqualTo("1.0");
// (This widening conversion is actually lossy)
assertThat(gson.toJson(Long.MAX_VALUE - 10L, Double.class)).isEqualTo(Double.toString(Long.MAX_VALUE - 10L));
assertThat(gson.toJson(Long.MAX_VALUE - 10L, Double.class)).isEqualTo(Double.toString((double) (Long.MAX_VALUE - 10L)));
}
@Test
@ -544,7 +544,7 @@ public class PrimitiveTest {
@Test
public void testDoubleNaNSerialization() {
Gson gson = new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create();
double nan = Double.NaN;
assertThat(gson.toJson(nan)).isEqualTo("NaN");
assertThat(gson.toJson(Double.NaN)).isEqualTo("NaN");
@ -573,7 +573,7 @@ public class PrimitiveTest {
@Test
public void testFloatNaNSerialization() {
Gson gson = new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create();
float nan = Float.NaN;
assertThat(gson.toJson(nan)).isEqualTo("NaN");
assertThat(gson.toJson(Float.NaN)).isEqualTo("NaN");
@ -611,7 +611,7 @@ public class PrimitiveTest {
@Test
public void testDoubleInfinitySerialization() {
Gson gson = new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create();
double infinity = Double.POSITIVE_INFINITY;
assertThat(gson.toJson(infinity)).isEqualTo("Infinity");
assertThat(gson.toJson(Double.POSITIVE_INFINITY)).isEqualTo("Infinity");
@ -640,7 +640,7 @@ public class PrimitiveTest {
@Test
public void testFloatInfinitySerialization() {
Gson gson = new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create();
float infinity = Float.POSITIVE_INFINITY;
assertThat(gson.toJson(infinity)).isEqualTo("Infinity");
assertThat(gson.toJson(Float.POSITIVE_INFINITY)).isEqualTo("Infinity");
@ -678,7 +678,7 @@ public class PrimitiveTest {
@Test
public void testNegativeInfinitySerialization() {
Gson gson = new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create();
double negativeInfinity = Double.NEGATIVE_INFINITY;
assertThat(gson.toJson(negativeInfinity)).isEqualTo("-Infinity");
assertThat(gson.toJson(Double.NEGATIVE_INFINITY)).isEqualTo("-Infinity");
@ -707,7 +707,7 @@ public class PrimitiveTest {
@Test
public void testNegativeInfinityFloatSerialization() {
Gson gson = new GsonBuilder().setLenient().serializeSpecialFloatingPointValues().create();
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).serializeSpecialFloatingPointValues().create();
float negativeInfinity = Float.NEGATIVE_INFINITY;
assertThat(gson.toJson(negativeInfinity)).isEqualTo("-Infinity");
assertThat(gson.toJson(Float.NEGATIVE_INFINITY)).isEqualTo("-Infinity");
@ -1060,6 +1060,7 @@ public class PrimitiveTest {
@Test
public void testStringsAsBooleans() {
String json = "['true', 'false', 'TRUE', 'yes', '1']";
assertThat( gson.<List<Boolean>>fromJson(json, new TypeToken<List<Boolean>>() {}.getType())).isEqualTo(Arrays.asList(true, false, true, false, false));
List<Boolean> deserialized = gson.fromJson(json, new TypeToken<List<Boolean>>() {});
assertThat(deserialized).isEqualTo(Arrays.asList(true, false, true, false, false));
}
}

View File

@ -78,6 +78,7 @@ public class PrintFormattingTest {
assertThat(json).contains("field2");
}
@SuppressWarnings("LoopOverCharArray")
private static void assertContainsNoWhiteSpace(String str) {
for (char c : str.toCharArray()) {
assertThat(Character.isWhitespace(c)).isFalse();

View File

@ -18,6 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonStreamParser;
@ -155,12 +156,14 @@ public class ReadersWritersTest {
final StringBuilder stringBuilder = new StringBuilder();
int toStringCallCount = 0;
@CanIgnoreReturnValue
@Override
public Appendable append(char c) throws IOException {
stringBuilder.append(c);
return this;
}
@CanIgnoreReturnValue
@Override
public Appendable append(CharSequence csq) throws IOException {
if (csq == null) {
@ -170,6 +173,7 @@ public class ReadersWritersTest {
return this;
}
@CanIgnoreReturnValue
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
if (csq == null) {

View File

@ -44,6 +44,20 @@ public class ReflectionAccessTest {
}
}
private static JsonIOException assertInaccessibleException(String json, Class<?> toDeserialize) {
Gson gson = new Gson();
try {
gson.fromJson(json, toDeserialize);
throw new AssertionError("Missing exception; test has to be run with `--illegal-access=deny`");
} catch (JsonSyntaxException e) {
throw new AssertionError("Unexpected exception; test has to be run with `--illegal-access=deny`", e);
} catch (JsonIOException expected) {
assertThat(expected).hasMessageThat().endsWith("\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#reflection-inaccessible");
// Return exception for further assertions
return expected;
}
}
/**
* Test serializing an instance of a non-accessible internal class, but where
* Gson supports serializing one of its superinterfaces.
@ -63,14 +77,19 @@ public class ReflectionAccessTest {
// But deserialization should fail
Class<?> internalClass = Collections.emptyList().getClass();
try {
gson.fromJson("[]", internalClass);
fail("Missing exception; test has to be run with `--illegal-access=deny`");
} catch (JsonSyntaxException e) {
fail("Unexpected exception; test has to be run with `--illegal-access=deny`");
} catch (JsonIOException expected) {
assertThat(expected).hasMessageThat().startsWith("Failed making constructor 'java.util.Collections$EmptyList()' accessible;"
+ " either increase its visibility or write a custom InstanceCreator or TypeAdapter for its declaring type: ");
}
JsonIOException exception = assertInaccessibleException("[]", internalClass);
// Don't check exact class name because it is a JDK implementation detail
assertThat(exception).hasMessageThat().startsWith("Failed making constructor '");
assertThat(exception).hasMessageThat().contains("' accessible; either increase its visibility or"
+ " write a custom InstanceCreator or TypeAdapter for its declaring type: ");
}
@Test
public void testInaccessibleField() {
JsonIOException exception = assertInaccessibleException("{}", Throwable.class);
// Don't check exact field name because it is a JDK implementation detail
assertThat(exception).hasMessageThat().startsWith("Failed making field 'java.lang.Throwable#");
assertThat(exception).hasMessageThat().contains("' accessible; either increase its visibility or"
+ " write a custom TypeAdapter for its declaring type.");
}
}

View File

@ -17,6 +17,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ -124,6 +125,7 @@ public final class RuntimeTypeAdapterFactoryFunctionalTest {
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
@CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
@ -143,6 +145,7 @@ public final class RuntimeTypeAdapterFactoryFunctionalTest {
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
@CanIgnoreReturnValue
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}

View File

@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.Strictness;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import org.junit.Before;
import org.junit.Test;
@ -39,7 +40,7 @@ public class SecurityTest {
@Before
public void setUp() throws Exception {
gsonBuilder = new GsonBuilder().setLenient();
gsonBuilder = new GsonBuilder().setStrictness(Strictness.LENIENT);
}
@Test

View File

@ -17,16 +17,10 @@
package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.common.base.Splitter;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
@ -150,7 +144,7 @@ public final class StreamingTypeAdaptersTest {
public void testDeserialize1dArray() throws IOException {
TypeAdapter<double[]> arrayAdapter = miniGson.getAdapter(new TypeToken<double[]>() {});
double[] array = arrayAdapter.fromJson("[1.0,2.0,3.0]");
assertWithMessage(Arrays.toString(array)).that(Arrays.equals(new double[]{1.0, 2.0, 3.0}, array)).isTrue();
assertThat(array).isEqualTo(new double[]{1.0, 2.0, 3.0});
}
@Test
@ -165,21 +159,21 @@ public final class StreamingTypeAdaptersTest {
TypeAdapter<double[][]> arrayAdapter = miniGson.getAdapter(new TypeToken<double[][]>() {});
double[][] array = arrayAdapter.fromJson("[[1.0,2.0],[3.0]]");
double[][] expected = { {1.0, 2.0 }, { 3.0 } };
assertWithMessage(Arrays.toString(array)).that(Arrays.deepEquals(expected, array)).isTrue();
assertThat(array).isEqualTo(expected);
}
@Test
public void testNullSafe() {
TypeAdapter<Person> typeAdapter = new TypeAdapter<Person>() {
@Override public Person read(JsonReader in) throws IOException {
String[] values = in.nextString().split(",");
return new Person(values[0], Integer.parseInt(values[1]));
List<String> values = Splitter.on(',').splitToList(in.nextString());
return new Person(values.get(0), Integer.parseInt(values.get(1)));
}
@Override public void write(JsonWriter out, Person person) throws IOException {
out.value(person.name + "," + person.age);
}
};
Gson gson = new GsonBuilder().setLenient().registerTypeAdapter(
Gson gson = new GsonBuilder().setStrictness(Strictness.LENIENT).registerTypeAdapter(
Person.class, typeAdapter).create();
Truck truck = new Truck();
truck.horsePower = 1.0D;
@ -194,8 +188,11 @@ public final class StreamingTypeAdaptersTest {
try {
gson.fromJson(json, Truck.class);
fail();
} catch (JsonSyntaxException expected) {}
gson = new GsonBuilder().setLenient().registerTypeAdapter(Person.class, typeAdapter.nullSafe()).create();
} catch (JsonSyntaxException expected) {
assertThat(expected).hasMessageThat().isEqualTo("java.lang.IllegalStateException: Expected a string but was NULL at line 1 column 33 (char ',') path $.passengers[0]"
+ "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#adapter-not-null-safe");
}
gson = new GsonBuilder().setStrictness(Strictness.LENIENT).registerTypeAdapter(Person.class, typeAdapter.nullSafe()).create();
assertThat(gson.toJson(truck, Truck.class))
.isEqualTo("{\"horsePower\":1.0,\"passengers\":[null,\"jesse,30\"]}");
truck = gson.fromJson(json, Truck.class);
@ -215,7 +212,7 @@ public final class StreamingTypeAdaptersTest {
+ "'left':{'label':'left','left':null,'right':null},"
+ "'right':{'label':'right','left':null,'right':null}}");
}
@Test
public void testFromJsonTree() {
JsonObject truckObject = new JsonObject();

View File

@ -154,7 +154,7 @@ public class StringTest {
String value = gson.fromJson(json, String.class);
assertThat(value).isEqualTo("abc=");
json = "'abc\u003d'";
json = "'abc\\u003d'";
value = gson.fromJson(json, String.class);
assertThat(value).isEqualTo("abc=");
}

View File

@ -29,7 +29,6 @@ import com.google.gson.stream.JsonReader;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.junit.Test;
@ -103,16 +102,12 @@ public class ToNumberPolicyFunctionalTest {
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
.setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
.create();
List<Object> expected = new LinkedList<>();
expected.add(null);
expected.add(10L);
expected.add(10.0);
Type objectCollectionType = new TypeToken<Collection<Object>>() { }.getType();
Collection<Object> objects = gson.fromJson("[null,10,10.0]", objectCollectionType);
assertThat(objects).isEqualTo(expected);
assertThat(objects).containsExactly(null, 10L, 10.0).inOrder();
Type numberCollectionType = new TypeToken<Collection<Number>>() { }.getType();
Collection<Object> numbers = gson.fromJson("[null,10,10.0]", numberCollectionType);
assertThat(numbers).isEqualTo(expected);
assertThat(numbers).containsExactly(null, 10L, 10.0).inOrder();
}
@Test

View File

@ -18,15 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
@ -55,7 +47,7 @@ public class TreeTypeAdaptersTest {
@Before
public void setUp() {
gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Id.class, new IdTreeTypeAdapter())
.create();
course = new Course<>(COURSE_ID, 4,
@ -80,6 +72,7 @@ public class TreeTypeAdaptersTest {
assertThat(target.getId().getValue()).isEqualTo("1");
}
@SuppressWarnings("UnusedTypeParameter")
private static final class Id<R> {
final String value;
@SuppressWarnings("unused")

View File

@ -18,15 +18,7 @@ package com.google.gson.functional;
import static com.google.common.truth.Truth.assertThat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
@ -37,7 +29,7 @@ public final class TypeAdapterPrecedenceTest {
@Test
public void testNonstreamingFollowedByNonstreaming() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Foo.class, newSerializer("serializer 1"))
.registerTypeAdapter(Foo.class, newSerializer("serializer 2"))
.registerTypeAdapter(Foo.class, newDeserializer("deserializer 1"))
@ -50,7 +42,7 @@ public final class TypeAdapterPrecedenceTest {
@Test
public void testStreamingFollowedByStreaming() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Foo.class, newTypeAdapter("type adapter 1"))
.registerTypeAdapter(Foo.class, newTypeAdapter("type adapter 2"))
.create();
@ -61,7 +53,7 @@ public final class TypeAdapterPrecedenceTest {
@Test
public void testSerializeNonstreamingTypeAdapterFollowedByStreamingTypeAdapter() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Foo.class, newSerializer("serializer"))
.registerTypeAdapter(Foo.class, newDeserializer("deserializer"))
.registerTypeAdapter(Foo.class, newTypeAdapter("type adapter"))
@ -73,19 +65,19 @@ public final class TypeAdapterPrecedenceTest {
@Test
public void testStreamingFollowedByNonstreaming() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeAdapter(Foo.class, newTypeAdapter("type adapter"))
.registerTypeAdapter(Foo.class, newSerializer("serializer"))
.registerTypeAdapter(Foo.class, newDeserializer("deserializer"))
.create();
assertThat(gson.toJson(new Foo("foo"))).isEqualTo("\"foo via serializer\"");
assertThat( gson.fromJson("foo", Foo.class).name).isEqualTo("foo via deserializer");
assertThat(gson.fromJson("foo", Foo.class).name).isEqualTo("foo via deserializer");
}
@Test
public void testStreamingHierarchicalFollowedByNonstreaming() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeHierarchyAdapter(Foo.class, newTypeAdapter("type adapter"))
.registerTypeAdapter(Foo.class, newSerializer("serializer"))
.registerTypeAdapter(Foo.class, newDeserializer("deserializer"))
@ -108,7 +100,7 @@ public final class TypeAdapterPrecedenceTest {
@Test
public void testStreamingHierarchicalFollowedByNonstreamingHierarchical() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeHierarchyAdapter(Foo.class, newSerializer("serializer"))
.registerTypeHierarchyAdapter(Foo.class, newDeserializer("deserializer"))
.registerTypeHierarchyAdapter(Foo.class, newTypeAdapter("type adapter"))
@ -120,7 +112,7 @@ public final class TypeAdapterPrecedenceTest {
@Test
public void testNonstreamingHierarchicalFollowedByNonstreaming() {
Gson gson = new GsonBuilder()
.setLenient()
.setStrictness(Strictness.LENIENT)
.registerTypeHierarchyAdapter(Foo.class, newSerializer("hierarchical"))
.registerTypeHierarchyAdapter(Foo.class, newDeserializer("hierarchical"))
.registerTypeAdapter(Foo.class, newSerializer("non hierarchical"))

View File

@ -136,7 +136,7 @@ public final class TypeHierarchyAdapterTest {
/** This behaviour changed in Gson 2.1; it used to throw. */
@Test
public void testRegisterSubTypeFirstAllowed() {
new GsonBuilder()
Gson unused = new GsonBuilder()
.registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
.registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
.create();

View File

@ -28,7 +28,6 @@ import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassOverridingEquals;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;

Some files were not shown because too many files have changed in this diff Show More