Merge remote-tracking branch 'origin/main'
# Conflicts: # .github/ISSUE_TEMPLATE/config.yml # .github/dependabot.yml # .github/pull_request_template.md # .github/workflows/build.yml # .github/workflows/check-android-compatibility.yml # .github/workflows/check-api-compatibility.yml # .github/workflows/cifuzz.yml # .github/workflows/codeql-analysis.yml # extras/pom.xml # extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java # extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java # extras/src/main/java/com/google/gson/interceptors/Intercept.java # extras/src/main/java/com/google/gson/interceptors/InterceptorFactory.java # extras/src/main/java/com/google/gson/interceptors/JsonPostDeserializer.java # extras/src/main/java/com/google/gson/typeadapters/PostConstructAdapterFactory.java # extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java # extras/src/main/java/com/google/gson/typeadapters/UtcDateTypeAdapter.java # extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java # extras/src/test/java/com/google/gson/interceptors/InterceptorTest.java # extras/src/test/java/com/google/gson/typeadapters/PostConstructAdapterFactoryTest.java # extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java # extras/src/test/java/com/google/gson/typeadapters/UtcDateTypeAdapterTest.java # graal-native-image-test/pom.xml # graal-native-image-test/src/test/java/com/google/gson/native_test/Java17RecordReflectionTest.java # graal-native-image-test/src/test/java/com/google/gson/native_test/ReflectionTest.java # gson/pom.xml # gson/src/main/java/com/google/gson/Gson.java # gson/src/main/java/com/google/gson/GsonBuilder.java # gson/src/main/java/com/google/gson/JsonParser.java # gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java # gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java # gson/src/main/java/com/google/gson/internal/bind/DefaultDateTypeAdapter.java # gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java # gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java # gson/src/main/java/com/google/gson/stream/JsonReader.java # gson/src/main/java/com/google/gson/stream/JsonTreeWriter.java # gson/src/main/java/com/google/gson/stream/JsonWriter.java # gson/src/test/java/com/google/gson/CommentsTest.java # gson/src/test/java/com/google/gson/GsonTest.java # gson/src/test/java/com/google/gson/MixedStreamTest.java # gson/src/test/java/com/google/gson/ToNumberPolicyTest.java # gson/src/test/java/com/google/gson/functional/ArrayTest.java # gson/src/test/java/com/google/gson/functional/ConcurrencyTest.java # gson/src/test/java/com/google/gson/functional/CustomDeserializerTest.java # gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java # gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java # gson/src/test/java/com/google/gson/functional/EnumWithObfuscatedTest.java # gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java # gson/src/test/java/com/google/gson/functional/FormattingStyleTest.java # gson/src/test/java/com/google/gson/functional/InstanceCreatorTest.java # gson/src/test/java/com/google/gson/functional/InternationalizationTest.java # gson/src/test/java/com/google/gson/functional/Java17RecordTest.java # gson/src/test/java/com/google/gson/functional/JavaUtilConcurrentAtomicTest.java # gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java # gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java # gson/src/test/java/com/google/gson/functional/JsonParserTest.java # gson/src/test/java/com/google/gson/functional/NullObjectAndFieldTest.java # gson/src/test/java/com/google/gson/functional/ObjectTest.java # gson/src/test/java/com/google/gson/functional/PrimitiveTest.java # gson/src/test/java/com/google/gson/functional/ReflectionAccessTest.java # gson/src/test/java/com/google/gson/functional/StreamingTypeAdaptersTest.java # gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java # gson/src/test/java/com/google/gson/functional/TypeAdapterPrecedenceTest.java # gson/src/test/java/com/google/gson/internal/bind/Java17ReflectiveTypeAdapterFactoryTest.java # gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java # gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java # gson/src/test/java/com/google/gson/internal/reflect/Java17ReflectionHelperTest.java # gson/src/test/java/com/google/gson/stream/JsonReaderTest.java # metrics/pom.xml # metrics/src/main/java/com/google/gson/metrics/BagOfPrimitives.java # metrics/src/main/java/com/google/gson/metrics/BagOfPrimitivesDeserializationBenchmark.java # metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java # metrics/src/main/java/com/google/gson/metrics/NonUploadingCaliperRunner.java # metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java # metrics/src/main/java/com/google/gson/metrics/SerializationBenchmark.java # pom.xml # proto/pom.xml # proto/src/main/java/com/google/gson/protobuf/ProtoTypeAdapter.java # proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithAnnotationsTest.java # proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithComplexAndRepeatedFieldsTest.java # proto/src/test/java/com/google/gson/protobuf/functional/ProtosWithPrimitiveTypesTest.java # shrinker-test/pom.xml # shrinker-test/src/main/java/com/example/ClassWithExposeAnnotation.java # shrinker-test/src/main/java/com/example/ClassWithHasArgsConstructor.java # shrinker-test/src/main/java/com/example/ClassWithJsonAdapterAnnotation.java # shrinker-test/src/main/java/com/example/ClassWithNoArgsConstructor.java # shrinker-test/src/main/java/com/example/ClassWithUnreferencedHasArgsConstructor.java # shrinker-test/src/main/java/com/example/ClassWithUnreferencedNoArgsConstructor.java # shrinker-test/src/main/java/com/example/ClassWithVersionAnnotations.java # shrinker-test/src/main/java/com/example/GenericClasses.java # shrinker-test/src/main/java/com/example/Main.java # shrinker-test/src/main/java/com/example/NoSerializedNameMain.java # shrinker-test/src/main/java/com/example/TestExecutor.java # shrinker-test/src/main/java/com/example/UnusedClass.java # shrinker-test/src/test/java/com/google/gson/it/ShrinkingIT.java
This commit is contained in:
commit
ff4b1a7656
|
@ -0,0 +1,5 @@
|
||||||
|
# Ignore commit which reformatted code
|
||||||
|
2c94c757a6a9426cc2fe47bc1c63f69e7c73b7b4
|
||||||
|
|
||||||
|
# Ignore commit which changed line endings consistently to LF
|
||||||
|
c2a0e4634a2100494159add78db2ee06f5eb9be6
|
|
@ -1,57 +1,59 @@
|
||||||
# Gson Design Document
|
# Gson Design Document
|
||||||
|
|
||||||
This document presents issues that we faced while designing Gson. It is meant for advanced users or developers working on Gson. If you are interested in learning how to use Gson, see its user guide.
|
This document presents issues that we faced while designing Gson. It is meant for advanced users or developers working on Gson. If you are interested in learning how to use Gson, see its user guide.
|
||||||
|
|
||||||
**Navigating the Json tree or the target Type Tree while deserializing**
|
Some information in this document is outdated and does not reflect the current state of Gson. This information can however still be relevant for understanding the history of Gson.
|
||||||
|
|
||||||
When you are deserializing a Json string into an object of desired type, you can either navigate the tree of the input, or the type tree of the desired type. Gson uses the latter approach of navigating the type of the target object. This keeps you in tight control of instantiating only the type of objects that you are expecting (essentially validating the input against the expected "schema"). By doing this, you also ignore any extra fields that the Json input has but were not expected.
|
## Navigating the Json tree or the target Type Tree while deserializing
|
||||||
|
|
||||||
As part of Gson, we wrote a general purpose ObjectNavigator that can take any object and navigate through its fields calling a visitor of your choice.
|
When you are deserializing a Json string into an object of desired type, you can either navigate the tree of the input, or the type tree of the desired type. Gson uses the latter approach of navigating the type of the target object. This keeps you in tight control of instantiating only the type of objects that you are expecting (essentially validating the input against the expected "schema"). By doing this, you also ignore any extra fields that the Json input has but were not expected.
|
||||||
|
|
||||||
**Supporting richer serialization semantics than deserialization semantics**
|
As part of Gson, we wrote a general purpose ObjectNavigator that can take any object and navigate through its fields calling a visitor of your choice.
|
||||||
|
|
||||||
Gson supports serialization of arbitrary collections, but can only deserialize genericized collections. this means that Gson can, in some cases, fail to deserialize Json that it wrote. This is primarily a limitation of the Java type system since when you encounter a Json array of arbitrary types there is no way to detect the types of individual elements. We could have chosen to restrict the serialization to support only generic collections, but chose not to.This is because often the user of the library are concerned with either serialization or deserialization, but not both. In such cases, there is no need to artificially restrict the serialization capabilities.
|
## Supporting richer serialization semantics than deserialization semantics
|
||||||
|
|
||||||
**Supporting serialization and deserialization of classes that are not under your control and hence can not be modified**
|
Gson supports serialization of arbitrary collections, but can only deserialize genericized collections. this means that Gson can, in some cases, fail to deserialize Json that it wrote. This is primarily a limitation of the Java type system since when you encounter a Json array of arbitrary types there is no way to detect the types of individual elements. We could have chosen to restrict the serialization to support only generic collections, but chose not to. This is because often the user of the library are concerned with either serialization or deserialization, but not both. In such cases, there is no need to artificially restrict the serialization capabilities.
|
||||||
|
|
||||||
Some Json libraries use annotations on fields or methods to indicate which fields should be used for Json serialization. That approach essentially precludes the use of classes from JDK or third-party libraries. We solved this problem by defining the notion of Custom serializers and deserializers. This approach is not new, and was used by the JAX-RPC technology to solve essentially the same problem.
|
## Supporting serialization and deserialization of classes that are not under your control and hence can not be modified
|
||||||
|
|
||||||
**Using Checked vs Unchecked exceptions to indicate a parsing error**
|
Some Json libraries use annotations on fields or methods to indicate which fields should be used for Json serialization. That approach essentially precludes the use of classes from JDK or third-party libraries. We solved this problem by defining the notion of custom serializers and deserializers. This approach is not new, and was used by the JAX-RPC technology to solve essentially the same problem.
|
||||||
|
|
||||||
We chose to use unchecked exceptions to indicate a parsing failure. This is primarily done because usually the client can not recover from bad input, and hence forcing them to catch a checked exception results in sloppy code in the catch() block.
|
## Using Checked vs Unchecked exceptions to indicate a parsing error
|
||||||
|
|
||||||
**Creating class instances for deserialization**
|
We chose to use unchecked exceptions to indicate a parsing failure. This is primarily done because usually the client can not recover from bad input, and hence forcing them to catch a checked exception results in sloppy code in the `catch()` block.
|
||||||
|
|
||||||
Gson needs to create a dummy class instance before it can deserialize Json data into its fields. We could have used Guice to get such an instance, but that would have resulted in a dependency on Guice. Moreover, it probably would have done the wrong thing since Guice is expected to return a valid instance, whereas we need to create a dummy one. Worse, Gson would overwrite the fields of that instance with the incoming data there by modifying the instance for all subsequent Guice injections. This is clearly not a desired behavior. Hence, we create class instances by invoking the parameterless constructor. We also handle the primitive types, enums, collections, sets, maps and trees as a special case.
|
## Creating class instances for deserialization
|
||||||
|
|
||||||
To solve the problem of supporting unmodifiable types, we use custom instance creators. So, if you want to use a library types that does not define a default constructor (for example, Money class), then you can register an instance creator that returns a dummy instance when asked.
|
Gson needs to create a dummy class instance before it can deserialize Json data into its fields. We could have used Guice to get such an instance, but that would have resulted in a dependency on Guice. Moreover, it probably would have done the wrong thing since Guice is expected to return a valid instance, whereas we need to create a dummy one. Worse, Gson would overwrite the fields of that instance with the incoming data thereby modifying the instance for all subsequent Guice injections. This is clearly not a desired behavior. Hence, we create class instances by invoking the parameterless constructor. We also handle the primitive types, enums, collections, sets, maps and trees as a special case.
|
||||||
|
|
||||||
**Using fields vs getters to indicate Json elements**
|
To solve the problem of supporting unmodifiable types, we use custom instance creators. So, if you want to use a library type that does not define a default constructor (for example, `Money` class), then you can register an instance creator that returns a dummy instance when asked.
|
||||||
|
|
||||||
Some Json libraries use the getters of a type to deduce the Json elements. We chose to use all fields (up the inheritance hierarchy) that are not transient, static, or synthetic. We did this because not all classes are written with suitably named getters. Moreover, getXXX or isXXX might be semantic rather than indicating properties.
|
## Using fields vs getters to indicate Json elements
|
||||||
|
|
||||||
However, there are good arguments to support properties as well. We intend to enhance Gson in a latter version to support properties as an alternate mapping for indicating Json fields. For now, Gson is fields-based.
|
Some Json libraries use the getters of a type to deduce the Json elements. We chose to use all fields (up the inheritance hierarchy) that are not transient, static, or synthetic. We did this because not all classes are written with suitably named getters. Moreover, `getXXX` or `isXXX` might be semantic rather than indicating properties.
|
||||||
|
|
||||||
**Why are most classes in Gson marked as final?**
|
However, there are good arguments to support properties as well. We intend to enhance Gson in a later version to support properties as an alternate mapping for indicating Json fields. For now, Gson is fields-based.
|
||||||
|
|
||||||
While Gson provides a fairly extensible architecture by providing pluggable serializers and deserializers, Gson classes were not specifically designed to be extensible. Providing non-final classes would have allowed a user to legitimately extend Gson classes, and then expect that behavior to work in all subsequent revisions. We chose to limit such use-cases by marking classes as final, and waiting until a good use-case emerges to allow extensibility. Marking a class final also has a minor benefit of providing additional optimization opportunities to Java compiler and virtual machine.
|
## Why are most classes in Gson marked as final?
|
||||||
|
|
||||||
**Why are inner interfaces and classes used heavily in Gson?**
|
While Gson provides a fairly extensible architecture by providing pluggable serializers and deserializers, Gson classes were not specifically designed to be extensible. Providing non-final classes would have allowed a user to legitimately extend Gson classes, and then expect that behavior to work in all subsequent revisions. We chose to limit such use-cases by marking classes as final, and waiting until a good use-case emerges to allow extensibility. Marking a class final also has a minor benefit of providing additional optimization opportunities to Java compiler and virtual machine.
|
||||||
|
|
||||||
Gson uses inner classes substantially. Many of the public interfaces are inner interfaces too (see JsonSerializer.Context or JsonDeserializer.Context as an example). These are primarily done as a matter of style. For example, we could have moved JsonSerializer.Context to be a top-level class JsonSerializerContext, but chose not to do so. However, if you can give us good reasons to rename it alternately, we are open to changing this philosophy.
|
## Why are inner interfaces and classes used heavily in Gson?
|
||||||
|
|
||||||
**Why do you provide two ways of constructing Gson?**
|
Gson uses inner classes substantially. Many of the public interfaces are inner interfaces too (see `JsonSerializer.Context` or `JsonDeserializer.Context` as an example). These are primarily done as a matter of style. For example, we could have moved `JsonSerializer.Context` to be a top-level class `JsonSerializerContext`, but chose not to do so. However, if you can give us good reasons to rename it alternately, we are open to changing this philosophy.
|
||||||
|
|
||||||
Gson can be constructed in two ways: by invoking new Gson() or by using a GsonBuilder. We chose to provide a simple no-args constructor to handle simple use-cases for Gson where you want to use default options, and quickly want to get going with writing code. For all other situations, where you need to configure Gson with options such as formatters, version controls etc, we use a builder pattern. The builder pattern allows a user to specify multiple optional settings for what essentially become constructor parameters for Gson.
|
## Why do you provide two ways of constructing Gson?
|
||||||
|
|
||||||
**Comparing Gson with Alternate Approaches**
|
Gson can be constructed in two ways: by invoking `new Gson()` or by using a `GsonBuilder`. We chose to provide a simple no-args constructor to handle simple use-cases for Gson where you want to use default options, and quickly want to get going with writing code. For all other situations, where you need to configure Gson with options such as formatters, version controls etc., we use a builder pattern. The builder pattern allows a user to specify multiple optional settings for what essentially become constructor parameters for Gson.
|
||||||
|
|
||||||
|
## Comparing Gson with alternate approaches
|
||||||
|
|
||||||
Note that these comparisons were done while developing Gson so these date back to mid to late 2007.
|
Note that these comparisons were done while developing Gson so these date back to mid to late 2007.
|
||||||
|
|
||||||
__Comparing Gson with org.json library__
|
### Comparing Gson with org.json library
|
||||||
|
|
||||||
org.json is a much lower-level library that can be used to write a toJson() method in a class. If you can not use Gson directly (may be because of platform restrictions regarding reflection), you could use org.json to hand-code a toJson method in each object.
|
org.json is a much lower-level library that can be used to write a `toJson()` method in a class. If you can not use Gson directly (maybe because of platform restrictions regarding reflection), you could use org.json to hand-code a `toJson` method in each object.
|
||||||
|
|
||||||
__Comparing Gson with org.json.simple library__
|
### Comparing Gson with org.json.simple library
|
||||||
|
|
||||||
org.json.simple library is very similar to org.json library and hence fairly low level. The key issue with this library is that it does not handle exceptions very well. In some cases it appeared to just eat the exception while in other cases it throws an "Error" rather than an exception.
|
org.json.simple library is very similar to org.json library and hence fairly low level. The key issue with this library is that it does not handle exceptions very well. In some cases it appeared to just eat the exception while in other cases it throws an "Error" rather than an exception.
|
||||||
|
|
13
README.md
13
README.md
|
@ -31,7 +31,8 @@ Gson can work with arbitrary Java objects including pre-existing objects that yo
|
||||||
|
|
||||||
There are a few open-source projects that can convert Java objects to JSON. However, most of them require that you place Java annotations in your classes; something that you can not do if you do not have access to the source-code. Most also do not fully support the use of Java Generics. Gson considers both of these as very important design goals.
|
There are a few open-source projects that can convert Java objects to JSON. However, most of them require that you place Java annotations in your classes; something that you can not do if you do not have access to the source-code. Most also do not fully support the use of Java Generics. Gson considers both of these as very important design goals.
|
||||||
|
|
||||||
:information_source: Gson is currently in maintenance mode; existing bugs will be fixed, but large new features will likely not be added. If you want to add a new feature, please first search for existing GitHub issues, or create a new one to discuss the feature and get feedback.
|
> [!NOTE]\
|
||||||
|
> Gson is currently in maintenance mode; existing bugs will be fixed, but large new features will likely not be added. If you want to add a new feature, please first search for existing GitHub issues, or create a new one to discuss the feature and get feedback.
|
||||||
|
|
||||||
### Goals
|
### Goals
|
||||||
* Provide simple `toJson()` and `fromJson()` methods to convert Java objects to JSON and vice-versa
|
* Provide simple `toJson()` and `fromJson()` methods to convert Java objects to JSON and vice-versa
|
||||||
|
@ -73,10 +74,10 @@ Despite supporting older Java versions, Gson also provides a JPMS module descrip
|
||||||
These are the optional Java Platform Module System (JPMS) JDK modules which Gson depends on.
|
These are the optional Java Platform Module System (JPMS) JDK modules which Gson depends on.
|
||||||
This only applies when running Java 9 or newer.
|
This only applies when running Java 9 or newer.
|
||||||
|
|
||||||
- `java.sql` (optional since Gson 2.8.9)
|
- `java.sql` (optional since Gson 2.8.9)\
|
||||||
When this module is present, Gson provides default adapters for some SQL date and time classes.
|
When this module is present, Gson provides default adapters for some SQL date and time classes.
|
||||||
|
|
||||||
- `jdk.unsupported`, respectively class `sun.misc.Unsafe` (optional)
|
- `jdk.unsupported`, respectively class `sun.misc.Unsafe` (optional)\
|
||||||
When this module is present, Gson can use the `Unsafe` class to create instances of classes without no-args constructor.
|
When this module is present, Gson can use the `Unsafe` class to create instances of classes without no-args constructor.
|
||||||
However, care should be taken when relying on this. `Unsafe` is not available in all environments and its usage has some pitfalls,
|
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()).
|
see [`GsonBuilder.disableJdkUnsafe()`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#disableJdkUnsafe()).
|
||||||
|
@ -95,7 +96,7 @@ Older Gson versions may also support lower API levels, however this has not been
|
||||||
* [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)
|
* [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
|
* [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.
|
Please use the ['gson' tag on StackOverflow](https://stackoverflow.com/questions/tagged/gson), [GitHub Discussions](https://github.com/google/gson/discussions) or the [google-gson Google group](https://groups.google.com/group/google-gson) to discuss Gson or to post questions.
|
||||||
|
|
||||||
### Related Content Created by Third Parties
|
### Related Content Created by Third Parties
|
||||||
* [Gson Tutorial](https://www.studytrails.com/java/json/java-google-json-introduction/) by `StudyTrails`
|
* [Gson Tutorial](https://www.studytrails.com/java/json/java-google-json-introduction/) by `StudyTrails`
|
||||||
|
@ -109,11 +110,11 @@ Gson uses Maven to build the project:
|
||||||
mvn clean verify
|
mvn clean verify
|
||||||
```
|
```
|
||||||
|
|
||||||
JDK 11 or newer is required for building, JDK 17 is recommended.
|
JDK 11 or newer is required for building, JDK 17 is recommended. Newer JDKs are currently not supported for building (but are supported when _using_ Gson).
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
See the [contributing guide](https://github.com/google/.github/blob/master/CONTRIBUTING.md).
|
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.
|
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.
|
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.
|
||||||
|
|
|
@ -37,6 +37,72 @@ This section was borrowed heavily from [Doclava release process](https://code.go
|
||||||
|
|
||||||
See [OSSRH Publish Guide](https://central.sonatype.org/publish/publish-guide/).
|
See [OSSRH Publish Guide](https://central.sonatype.org/publish/publish-guide/).
|
||||||
|
|
||||||
|
## Testing Maven release workflow locally
|
||||||
|
|
||||||
|
The following describes how to perform the steps of the release locally to verify that they work as desired.
|
||||||
|
|
||||||
|
**Warning:** Be careful with this, these steps might be outdated or incomplete. Doublecheck that you are working on a copy of your local Gson Git repository and make sure you have followed all steps. To be safe you can also temporarily turn off your internet connection to avoid accidentally pushing changes to the real remote Git or Maven repository.\
|
||||||
|
As an alternative to the steps described below you can instead [perform a dry run](https://maven.apache.org/maven-release/maven-release-plugin/usage.html#do-a-dry-run), though this might not behave identical to a real release.
|
||||||
|
|
||||||
|
1. Make a copy of your local Gson Git repository and only work with that copy
|
||||||
|
2. Make sure you are on the `main` branch
|
||||||
|
3. Create a temp directory outside the Gson directory\
|
||||||
|
In the following steps this will be called `#gson-remote-temp#`; replace this with the actual absolute file path of the directory, using only forward slashes. For example under Windows `C:\my-dir` becomes `C:/my-dir`.
|
||||||
|
4. Create the directory `#gson-remote-temp#/git-repo`
|
||||||
|
5. In that directory run
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git init --bare --initial-branch=main .
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Create the directory `#gson-remote-temp#/maven-repo`
|
||||||
|
7. Edit the root `pom.xml` of Gson
|
||||||
|
1. Change the `<developerConnection>` to
|
||||||
|
|
||||||
|
```txt
|
||||||
|
scm:git:file:///#gson-remote-temp#/git-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Change the `<url>` of the `<distributionManagement>` to
|
||||||
|
|
||||||
|
```txt
|
||||||
|
file:///#gson-remote-temp#/maven-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
3. If you don't want to use GPG, remove the `maven-gpg-plugin` entry from the 'release' profile.\
|
||||||
|
There is also an entry under `<pluginManagement>`; you can remove that as well.
|
||||||
|
8. Commit the changes using Git
|
||||||
|
9. Change the remote repository of the Git project
|
||||||
|
|
||||||
|
<!-- Uses `txt` instead of `sh` to avoid the `#` being highlighted in some way -->
|
||||||
|
```txt
|
||||||
|
git remote set-url origin file:///#gson-remote-temp#/git-repo
|
||||||
|
```
|
||||||
|
|
||||||
|
10. Push the changes
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can perform the steps of the release:
|
||||||
|
|
||||||
|
1. ```sh
|
||||||
|
mvn release:clean
|
||||||
|
```
|
||||||
|
|
||||||
|
2. ```sh
|
||||||
|
mvn release:prepare
|
||||||
|
```
|
||||||
|
|
||||||
|
3. ```sh
|
||||||
|
mvn release:perform
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Verify that `#gson-remote-temp#/git-repo` and `#gson-remote-temp#/maven-repo` contain all the desired changes
|
||||||
|
5. Afterwards delete all Gson files under `${user.home}/.m2/repository/com/google/code/gson` which have been installed in your local Maven repository during the release.\
|
||||||
|
Otherwise Maven might not download the real Gson artifacts with these version numbers, once they are released.
|
||||||
|
|
||||||
## Running Benchmarks or Tests on Android
|
## Running Benchmarks or Tests on Android
|
||||||
|
|
||||||
* Download vogar
|
* Download vogar
|
||||||
|
|
|
@ -1,357 +1,389 @@
|
||||||
# Troubleshooting Guide
|
# Troubleshooting Guide
|
||||||
|
|
||||||
This guide describes how to troubleshoot common issues when using Gson.
|
This guide describes how to troubleshoot common issues when using Gson.
|
||||||
|
|
||||||
<!-- The '<a id="..."></a>' anchors below are used to create stable links; don't remove or rename them -->
|
<!-- 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 -->
|
<!-- 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
|
## <a id="class-cast-exception"></a> `ClassCastException` when using deserialized object
|
||||||
|
|
||||||
**Symptom:** `ClassCastException` is thrown when accessing an object deserialized by Gson
|
**Symptom:** `ClassCastException` is thrown when accessing an object deserialized by Gson
|
||||||
|
|
||||||
**Reason:** Your code is most likely not type-safe
|
**Reason:** Your code is most likely not type-safe
|
||||||
|
|
||||||
**Solution:** Make sure your code adheres to the following:
|
**Solution:** Make sure your code adheres to the following:
|
||||||
|
|
||||||
- Avoid raw types: Instead of calling `fromJson(..., List.class)`, create for example a `TypeToken<List<MyClass>>`.
|
- Avoid raw types: Instead of calling `fromJson(..., List.class)`, create for example a `TypeToken<List<MyClass>>`.
|
||||||
See the [user guide](UserGuide.md#collections-examples) for more information.
|
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)).
|
- 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.
|
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](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.
|
- 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.
|
||||||
|
|
||||||
## <a id="reflection-inaccessible"></a> `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
|
**Symptom:** An exception with a message in the form 'module ... does not "opens ..." to unnamed module' is thrown
|
||||||
|
|
||||||
**Reason:** You use Gson by accident to access internal fields of third-party classes
|
**Reason:** You use Gson by accident to access internal fields of third-party classes
|
||||||
|
|
||||||
**Solution:** Write custom Gson [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) implementations for the affected classes or change the type of your data. If 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).
|
**Solution:** Write custom Gson [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) implementations for the affected classes or change the type of your data.
|
||||||
|
If you already wrote a custom adapter, but it is not used, see [this troubleshooting point](#custom-adapter-not-used).\
|
||||||
**Explanation:**
|
If 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).
|
||||||
|
|
||||||
When no built-in adapter for a type exists and no custom adapter has been registered, Gson falls back to using reflection to access the fields of a class (including `private` ones). Most likely you are seeing this error because you (by accident) rely on the reflection-based adapter for third-party classes. That should be avoided because you make yourself dependent on the implementation details of these classes which could change at any point. For the JDK it is also not possible anymore to access internal fields using reflection starting with JDK 17, see [JEP 403](https://openjdk.org/jeps/403).
|
**Explanation:**
|
||||||
|
|
||||||
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`.
|
When no built-in adapter for a type exists and no custom adapter has been registered, Gson falls back to using reflection to access the fields of a class (including `private` ones). Most likely you are seeing this error because you (by accident) rely on the reflection-based adapter for third-party classes. That should be avoided because you make yourself dependent on the implementation details of these classes which could change at any point. For the JDK it is also not possible anymore to access internal fields using reflection starting with JDK 17, see [JEP 403](https://openjdk.org/jeps/403).
|
||||||
|
|
||||||
## <a id="reflection-inaccessible-to-module-gson"></a> `InaccessibleObjectException`: 'module ... does not "opens ..." to module com.google.gson'
|
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`.
|
||||||
|
|
||||||
**Symptom:** An exception with a message in the form 'module ... does not "opens ..." to module com.google.gson' is thrown
|
## <a id="reflection-inaccessible-to-module-gson"></a> `InaccessibleObjectException`: 'module ... does not "opens ..." to module com.google.gson'
|
||||||
|
|
||||||
**Reason:**
|
**Symptom:** An exception with a message in the form 'module ... does not "opens ..." to module com.google.gson' is thrown
|
||||||
|
|
||||||
- If the reported package is your own package then you have not configured the module declaration of your project to allow Gson to use reflection on your classes.
|
**Reason:**
|
||||||
- If the reported package is from a third party library or the JDK see [this troubleshooting point](#inaccessibleobjectexception-module--does-not-opens--to-unnamed-module).
|
|
||||||
|
- If the reported package is your own package then you have not configured the module declaration of your project to allow Gson to use reflection on your classes.
|
||||||
**Solution:** Make sure the `module-info.java` file of your project allows Gson to use reflection on your classes, for example:
|
- If the reported package is from a third party library or the JDK see [this troubleshooting point](#inaccessibleobjectexception-module--does-not-opens--to-unnamed-module).
|
||||||
|
|
||||||
```java
|
**Solution:** Make sure the `module-info.java` file of your project allows Gson to use reflection on your classes, for example:
|
||||||
module mymodule {
|
|
||||||
requires com.google.gson;
|
```java
|
||||||
|
module mymodule {
|
||||||
opens mypackage to com.google.gson;
|
requires com.google.gson;
|
||||||
}
|
|
||||||
```
|
opens mypackage to com.google.gson;
|
||||||
|
}
|
||||||
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).
|
```
|
||||||
|
|
||||||
## <a id="android-app-random-names"></a> Android app not working in Release mode; random property names
|
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).
|
||||||
|
|
||||||
**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`, ...
|
## <a id="android-app-random-names"></a> Android app not working in Release mode; random property names
|
||||||
|
|
||||||
**Reason:** You probably have not configured ProGuard / R8 correctly
|
**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`, ...
|
||||||
|
|
||||||
**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.
|
**Reason:** You probably have not configured ProGuard / R8 correctly
|
||||||
|
|
||||||
## <a id="android-app-broken-after-app-update"></a> Android app unable to parse JSON after app update
|
**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.
|
||||||
|
|
||||||
**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
|
## <a id="android-app-broken-after-app-update"></a> Android app unable to parse JSON after app update
|
||||||
|
|
||||||
**Reason:** You probably have not configured ProGuard / R8 correctly; probably the fields names are being obfuscated and their naming changed between the versions of your app
|
**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
|
||||||
|
|
||||||
**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.
|
**Reason:** You probably have not configured ProGuard / R8 correctly; probably the field names are being obfuscated and their naming changed between the versions of your app
|
||||||
|
|
||||||
If you want to preserve backward compatibility for you app you can use [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) on the fields to specify the obfuscated name as alternate, for example: `@SerializedName(value = "myprop", alternate = "a")`
|
**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.
|
||||||
|
|
||||||
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.
|
If you want to preserve backward compatibility for you app you can use [`@SerializedName`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) on the fields to specify the obfuscated name as alternate, for example: `@SerializedName(value = "myprop", alternate = "a")`
|
||||||
|
|
||||||
## <a id="default-field-values-missing"></a> Default field values not present after deserialization
|
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.
|
||||||
|
|
||||||
**Symptom:** You have assign default values to fields but after deserialization the fields have their standard value (such as `null` or `0`)
|
## <a id="default-field-values-missing"></a> Default field values not present after deserialization
|
||||||
|
|
||||||
**Reason:** Gson cannot invoke the constructor of your class and falls back to JDK `Unsafe` (or similar means)
|
**Symptom:** You have assign default values to fields but after deserialization the fields have their standard value (such as `null` or `0`)
|
||||||
|
|
||||||
**Solution:** Make sure that the class:
|
**Reason:** Gson cannot invoke the constructor of your class and falls back to JDK `Unsafe` (or similar means)
|
||||||
|
|
||||||
- is `static` (explicitly or implicitly when it is a top-level class)
|
**Solution:** Make sure that the class:
|
||||||
- has a no-args constructor
|
|
||||||
|
- is `static` (explicitly or implicitly when it is a top-level class)
|
||||||
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.
|
- has a no-args constructor
|
||||||
|
|
||||||
## <a id="anonymous-local-null"></a> `null` values for anonymous and local classes
|
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.
|
||||||
|
|
||||||
**Symptom:** Objects of a class are always serialized as JSON `null` / always deserialized as Java `null`
|
## <a id="anonymous-local-null"></a> `null` values for anonymous and local classes
|
||||||
|
|
||||||
**Reason:** The class you are serializing or deserializing is an anonymous or a local class (or you have specified a custom `ExclusionStrategy`)
|
**Symptom:** Objects of a class are always serialized as JSON `null` / always deserialized as Java `null`
|
||||||
|
|
||||||
**Solution:** Convert the class to a `static` nested class. If the class is already `static` make sure you have not specified a Gson `ExclusionStrategy` which might exclude the class.
|
**Reason:** The class you are serializing or deserializing is an anonymous or a local class (or you have specified a custom `ExclusionStrategy`)
|
||||||
|
|
||||||
Notes:
|
**Solution:** Convert the class to a `static` nested class. If the class is already `static` make sure you have not specified a Gson `ExclusionStrategy` which might exclude the class.
|
||||||
|
|
||||||
- "double brace-initialization" also creates anonymous classes
|
Notes:
|
||||||
- Local record classes (feature added in Java 16) are supported by Gson and are not affected by this
|
|
||||||
|
- "double brace-initialization" also creates anonymous classes
|
||||||
## <a id="map-key-wrong-json"></a> Map keys having unexpected format in JSON
|
- Local record classes (feature added in Java 16) are supported by Gson and are not affected by this
|
||||||
|
|
||||||
**Symptom:** JSON output for `Map` keys is unexpected / cannot be deserialized again
|
## <a id="map-key-wrong-json"></a> Map keys having unexpected format in JSON
|
||||||
|
|
||||||
**Reason:** The `Map` key type is 'complex' and you have not configured the `GsonBuilder` properly
|
**Symptom:** JSON output for `Map` keys is unexpected / cannot be deserialized again
|
||||||
|
|
||||||
**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.
|
**Reason:** The `Map` key type is 'complex' and you have not configured the `GsonBuilder` properly
|
||||||
|
|
||||||
## <a id="malformed-json"></a> Parsing JSON fails with `MalformedJsonException`
|
**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.
|
||||||
|
|
||||||
**Symptom:** JSON parsing fails with `MalformedJsonException`
|
## <a id="malformed-json"></a> Parsing JSON fails with `MalformedJsonException`
|
||||||
|
|
||||||
**Reason:** The JSON data is actually malformed
|
**Symptom:** JSON parsing fails with `MalformedJsonException`
|
||||||
|
|
||||||
**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/).
|
**Reason:** The JSON data is actually malformed
|
||||||
|
|
||||||
For example, let's assume you want to deserialize the following JSON data:
|
**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/).
|
||||||
|
|
||||||
```json
|
For example, let's assume you want to deserialize the following JSON data:
|
||||||
{
|
|
||||||
"languages": [
|
```json
|
||||||
"English",
|
{
|
||||||
"French",
|
"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.
|
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]`).\
|
||||||
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.
|
The proper solution here is to fix the malformed JSON data.
|
||||||
|
|
||||||
## <a id="number-parsed-as-double"></a> Integral JSON number is parsed as `double`
|
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.
|
||||||
|
|
||||||
**Symptom:** JSON data contains an integral number such as `45` but Gson returns it as `double`
|
## <a id="number-parsed-as-double"></a> Integral JSON number is parsed as `double`
|
||||||
|
|
||||||
**Reason:** When parsing a JSON number as `Object`, Gson will by default create always return a `double`
|
**Symptom:** JSON data contains an integral number such as `45` but Gson returns it as `double`
|
||||||
|
|
||||||
**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
|
**Reason:** When parsing a JSON number as `Object`, Gson will by default always return a `double`
|
||||||
|
|
||||||
## <a id="default-lenient"></a> Malformed JSON not rejected
|
**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
|
||||||
|
|
||||||
**Symptom:** Gson parses malformed JSON without throwing any exceptions
|
## <a id="default-lenient"></a> Malformed JSON not rejected
|
||||||
|
|
||||||
**Reason:** Due to legacy reasons Gson performs parsing by default in lenient mode
|
**Symptom:** Gson parses malformed JSON without throwing any exceptions
|
||||||
|
|
||||||
**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)),
|
**Reason:** Due to legacy reasons Gson performs parsing by default in lenient mode
|
||||||
[`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))
|
**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)),
|
||||||
with `Strictness.STRICT` to overwrite the default lenient behavior of `Gson` and make these classes strictly adhere to the JSON specification.
|
[`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))
|
||||||
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)
|
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))
|
||||||
section "JSON Strictness handling" for alternative solutions.
|
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)
|
||||||
## <a id="unexpected-json-structure"></a> `IllegalStateException`: "Expected ... but was ..."
|
section "JSON Strictness handling" for alternative solutions.
|
||||||
|
|
||||||
**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was ..." is thrown
|
## <a id="unexpected-json-structure"></a> `IllegalStateException`: "Expected ... but was ..."
|
||||||
|
|
||||||
**Reason:** The JSON data does not have the correct format
|
**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was ..." is thrown
|
||||||
|
|
||||||
**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/).
|
**Reason:**
|
||||||
|
|
||||||
For example, let's assume you have the following Java class:
|
- The JSON data does not have the correct format
|
||||||
|
- Or, Gson has no built-in adapter for a type and tries to deserialize it as JSON object
|
||||||
```java
|
|
||||||
class WebPage {
|
**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/).
|
||||||
String languages;
|
|
||||||
}
|
For example, let's assume you have the following Java class:
|
||||||
```
|
|
||||||
|
```java
|
||||||
And you want to deserialize the following JSON data:
|
class WebPage {
|
||||||
|
String languages;
|
||||||
```json
|
}
|
||||||
{
|
```
|
||||||
"languages": ["English", "French"]
|
|
||||||
}
|
And you want to deserialize the following JSON data:
|
||||||
```
|
|
||||||
|
```json
|
||||||
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 (`$.`).
|
"languages": ["English", "French"]
|
||||||
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"
|
|
||||||
|
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`\
|
||||||
**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was NULL" is thrown
|
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`.
|
||||||
**Reason:**
|
|
||||||
|
If you are sure that the JSON data is correct and the exception message is "Expected BEGIN_OBJECT but was ...", then this might indicate that Gson has no built-in adapter for the type.
|
||||||
- A built-in adapter does not support JSON null values
|
Gson then tries to use reflection and expects that the data is a JSON object (hence the error message "Expected BEGIN_OBJECT ..."). In that case you have to write a custom [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) for that type. If you already wrote a custom adapter, but it is not used, see [this troubleshooting point](#custom-adapter-not-used).
|
||||||
- You have written a custom `TypeAdapter` which does not properly handle JSON null values
|
|
||||||
|
## <a id="adapter-not-null-safe"></a> `IllegalStateException`: "Expected ... but was NULL"
|
||||||
**Solution:** If this occurs for a custom adapter you wrote, add code similar to the following at the beginning of its `read` method:
|
|
||||||
|
**Symptom:** An `IllegalStateException` with a message in the form "Expected ... but was NULL" is thrown
|
||||||
```java
|
|
||||||
@Override
|
**Reason:**
|
||||||
public MyClass read(JsonReader in) throws IOException {
|
|
||||||
if (in.peek() == JsonToken.NULL) {
|
- A built-in adapter does not support JSON null values
|
||||||
in.nextNull();
|
- You have written a custom `TypeAdapter` which does not properly handle JSON null values
|
||||||
return null;
|
|
||||||
}
|
**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
|
||||||
```
|
public MyClass read(JsonReader in) throws IOException {
|
||||||
|
if (in.peek() == JsonToken.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.
|
in.nextNull();
|
||||||
|
return null;
|
||||||
## <a id="serialize-nulls"></a> Properties missing in JSON
|
}
|
||||||
|
|
||||||
**Symptom:** Properties are missing in the JSON output
|
...
|
||||||
|
}
|
||||||
**Reason:** Gson by default omits JSON null from the output (or: ProGuard / R8 is not configured correctly and removed unused fields)
|
```
|
||||||
|
|
||||||
**Solution:** Use [`GsonBuilder.serializeNulls()`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#serializeNulls())
|
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.
|
||||||
|
|
||||||
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).
|
## <a id="serialize-nulls"></a> Properties missing in JSON
|
||||||
|
|
||||||
## <a id="android-internal-fields"></a> JSON output changes for newer Android versions
|
**Symptom:** Properties are missing in the JSON output
|
||||||
|
|
||||||
**Symptom:** The JSON output differs when running on newer Android versions
|
**Reason:** Gson by default omits JSON null from the output (or: ProGuard / R8 is not configured correctly and removed unused fields)
|
||||||
|
|
||||||
**Reason:** You use Gson by accident to access internal fields of Android classes
|
**Solution:** Use [`GsonBuilder.serializeNulls()`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#serializeNulls())
|
||||||
|
|
||||||
**Solution:** Write custom Gson [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) implementations for the affected classes or change the type of your data
|
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).
|
||||||
|
|
||||||
**Explanation:**
|
## <a id="android-internal-fields"></a> JSON output changes for newer Android versions
|
||||||
|
|
||||||
When no built-in adapter for a type exists and no custom adapter has been registered, Gson falls back to using reflection to access the fields of a class (including `private` ones). Most likely you are experiencing this issue because you (by accident) rely on the reflection-based adapter for Android classes. That should be avoided because you make yourself dependent on the implementation details of these classes which could change at any point.
|
**Symptom:** The JSON output differs when running on newer Android versions
|
||||||
|
|
||||||
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`.
|
**Reason:** You use Gson by accident to access internal fields of Android classes
|
||||||
|
|
||||||
## <a id="json-static-fields"></a> JSON output contains values of `static` fields
|
**Solution:** Write custom Gson [`TypeAdapter`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) implementations for the affected classes or change the type of your data
|
||||||
|
|
||||||
**Symptom:** The JSON output contains values of `static` fields
|
**Explanation:**
|
||||||
|
|
||||||
**Reason:** You used `GsonBuilder.excludeFieldsWithModifiers` to overwrite the default excluded modifiers
|
When no built-in adapter for a type exists and no custom adapter has been registered, Gson falls back to using reflection to access the fields of a class (including `private` ones). Most likely you are experiencing this issue because you (by accident) rely on the reflection-based adapter for Android classes. That should be avoided because you make yourself dependent on the implementation details of these classes which could change at any point.
|
||||||
|
|
||||||
**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.
|
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`.
|
||||||
|
|
||||||
## <a id="no-such-method-error"></a> `NoSuchMethodError` when calling Gson methods
|
## <a id="json-static-fields"></a> JSON output contains values of `static` fields
|
||||||
|
|
||||||
**Symptom:** A `java.lang.NoSuchMethodError` is thrown when trying to call certain Gson methods
|
**Symptom:** The JSON output contains values of `static` fields
|
||||||
|
|
||||||
**Reason:**
|
**Reason:** You used `GsonBuilder.excludeFieldsWithModifiers` to overwrite the default excluded modifiers
|
||||||
|
|
||||||
- You have multiple versions of Gson on your classpath
|
**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.
|
||||||
- Or, the Gson version you compiled against is different from the one on your classpath
|
|
||||||
- Or, you are using a code shrinking tool such as ProGuard or R8 which removed methods from Gson
|
## <a id="no-such-method-error"></a> `NoSuchMethodError` when calling Gson methods
|
||||||
|
|
||||||
**Solution:** First disable any code shrinking tools such as ProGuard or R8 and check if the issue persists. If not, you have to tweak the configuration of that tool to not modify Gson classes. Otherwise verify that the Gson JAR on your classpath is the same you are compiling against, and that there is only one Gson JAR on your classpath. See [this Stack Overflow question](https://stackoverflow.com/q/227486) to find out where a class is loaded from. For example, for debugging you could include the following code:
|
**Symptom:** A `java.lang.NoSuchMethodError` is thrown when trying to call certain Gson methods
|
||||||
|
|
||||||
```java
|
**Reason:**
|
||||||
System.out.println(Gson.class.getProtectionDomain().getCodeSource().getLocation());
|
|
||||||
```
|
- You have multiple versions of Gson on your classpath
|
||||||
|
- Or, the Gson version you compiled against is different from the one on your classpath
|
||||||
If that fails with a `NullPointerException` you have to try one of the other ways to find out where a class is loaded from.
|
- Or, you are using a code shrinking tool such as ProGuard or R8 which removed methods from Gson
|
||||||
|
|
||||||
## <a id="duplicate-fields"></a> `IllegalArgumentException`: 'Class ... declares multiple JSON fields named '...''
|
**Solution:** First disable any code shrinking tools such as ProGuard or R8 and check if the issue persists. If not, you have to tweak the configuration of that tool to not modify Gson classes. Otherwise verify that the Gson JAR on your classpath is the same you are compiling against, and that there is only one Gson JAR on your classpath. See [this Stack Overflow question](https://stackoverflow.com/q/227486) to find out where a class is loaded from. For example, for debugging you could include the following code:
|
||||||
|
|
||||||
**Symptom:** An exception with the message 'Class ... declares multiple JSON fields named '...'' is thrown
|
```java
|
||||||
|
System.out.println(Gson.class.getProtectionDomain().getCodeSource().getLocation());
|
||||||
**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
|
If that fails with a `NullPointerException` you have to try one of the other ways to find out where a class is loaded from.
|
||||||
- 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
|
## <a id="duplicate-fields"></a> `IllegalArgumentException`: 'Class ... declares multiple JSON fields named '...''
|
||||||
|
|
||||||
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.
|
**Symptom:** An exception with the message 'Class ... declares multiple JSON fields named '...'' is thrown
|
||||||
|
|
||||||
**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.
|
**Reason:**
|
||||||
|
|
||||||
## <a id="java-lang-class-unsupported"></a> `UnsupportedOperationException` when serializing or deserializing `java.lang.Class`
|
- 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
|
||||||
**Symptom:** An `UnsupportedOperationException` is thrown when trying to serialize or deserialize `java.lang.Class`
|
- A field of your class has the same name as the field of a superclass
|
||||||
|
|
||||||
**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.
|
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 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:
|
**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.
|
||||||
|
|
||||||
```java
|
## <a id="java-lang-class-unsupported"></a> `UnsupportedOperationException` when serializing or deserializing `java.lang.Class`
|
||||||
Class.forName(jsonString, false, getClass().getClassLoader()).asSubclass(MyBaseClass.class)
|
|
||||||
```
|
**Symptom:** An `UnsupportedOperationException` is thrown when trying to serialize or deserialize `java.lang.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`.
|
**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.
|
||||||
|
|
||||||
## <a id="type-token-raw"></a> `IllegalStateException`: 'TypeToken must be created with a type argument' <br> `RuntimeException`: 'Missing type parameter'
|
**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:
|
||||||
|
|
||||||
**Symptom:** An `IllegalStateException` with the message 'TypeToken must be created with a type argument' is thrown.
|
```java
|
||||||
For older Gson versions a `RuntimeException` with message 'Missing type parameter' is thrown.
|
Class.forName(jsonString, false, getClass().getClassLoader()).asSubclass(MyBaseClass.class)
|
||||||
|
```
|
||||||
**Reason:**
|
|
||||||
|
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`.
|
||||||
- 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.
|
## <a id="custom-adapter-not-used"></a> Custom type adapter is not used
|
||||||
|
|
||||||
**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:
|
**Symptom:** You have registered a custom `TypeAdapter` (or `JsonSerializer` or `JsonDeserializer`) on a `GsonBuilder`, but Gson is not using your adapter
|
||||||
|
|
||||||
```
|
**Reason:**
|
||||||
# Keep generic signatures; needed for correct type resolution
|
|
||||||
-keepattributes Signature
|
- You registered the adapter for the wrong type
|
||||||
|
- Or, you are serializing or deserializing a subclass
|
||||||
# Keep class TypeToken (respectively its generic signature)
|
- Or, your custom `Gson` instance is not actually used
|
||||||
-keep class com.google.gson.reflect.TypeToken { *; }
|
|
||||||
|
**Solution:**
|
||||||
# Keep any (anonymous) classes extending TypeToken
|
|
||||||
-keep class * extends com.google.gson.reflect.TypeToken
|
- Debug your code and verify that the custom `Gson` instance on which you have registered the adapter is actually used. Possibly parts of your application are using a different `Gson` instance, or you are using a framework such as Spring which is using a different `Gson` instance with default configuration (in that case have a look at the framework-specific configuration options).
|
||||||
```
|
- Verify that you are registering the adapter for the correct type. `GsonBuilder.registerTypeAdapter(...)` takes the adapter as `Object` argument, so you will not see a compilation error when you provide the wrong type.
|
||||||
|
For example when you want to register an adapter for `MyClass`, you should call `registerTypeAdapter(MyClass.class, new MyClassAdapter())`.\
|
||||||
See also the [Android example](examples/android-proguard-example/README.md) for more information.
|
Also pay close attention to the package name, there are classes with the same name in different packages, such as `java.util.Date` and `java.sql.Date`.
|
||||||
|
- `registerTypeAdapter` only registers an adapter for the specified class, _but not for subclasses_. Use [`registerTypeHierarchyAdapter`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#registerTypeHierarchyAdapter(java.lang.Class,java.lang.Object))
|
||||||
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.
|
to also handle subclasses.
|
||||||
|
- Be careful with parameterized types for `registerTypeAdapter` because Gson only uses the adapter if there is an exact match for the types.
|
||||||
## <a id="r8-abstract-class"></a> `JsonIOException`: 'Abstract classes can't be instantiated!' (R8)
|
For example if you register an adapter for `List<Number>` it won't be used for `List` (raw type), `List<Integer>` or `ArrayList<Number>`.
|
||||||
|
You can solve this by writing a [`TypeAdapterFactory`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapterFactory.html) instead, which manually checks if the type matches.
|
||||||
**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).
|
- If you want to register an adapter for a primitive type such as `boolean`, you might also want to register it for the wrapper type `java.lang.Boolean`, and the other way around.
|
||||||
|
- The built-in adapters for `JsonElement` (and subclasses) and for `Object` cannot be overwritten. However, as workaround for a field of those types you can use the [`@JsonAdapter` annotation](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/JsonAdapter.html) to specify a custom adapter.
|
||||||
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.
|
|
||||||
|
## <a id="type-token-raw"></a> `IllegalStateException`: 'TypeToken must be created with a type argument' <br> `RuntimeException`: 'Missing type parameter'
|
||||||
**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.
|
|
||||||
|
**Symptom:** An `IllegalStateException` with the message 'TypeToken must be created with a type argument' is thrown.\
|
||||||
**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:
|
For older Gson versions a `RuntimeException` with message 'Missing type parameter' is thrown.
|
||||||
|
|
||||||
```
|
**Reason:**
|
||||||
# Keep the no-args constructor of the deserialized class
|
|
||||||
-keepclassmembers class com.example.MyClass {
|
- 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 `<...>`.
|
||||||
<init>();
|
- 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:
|
||||||
|
|
||||||
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.
|
```
|
||||||
|
# Keep generic signatures; needed for correct type resolution
|
||||||
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).
|
-keepattributes Signature
|
||||||
|
|
||||||
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.
|
# Keep class TypeToken (respectively its generic signature)
|
||||||
|
-keep class com.google.gson.reflect.TypeToken { *; }
|
||||||
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.
|
|
||||||
|
# Keep any (anonymous) classes extending TypeToken
|
||||||
## <a id="typetoken-type-variable"></a> `IllegalArgumentException`: 'TypeToken type argument must not contain a type variable'
|
-keep class * extends com.google.gson.reflect.TypeToken
|
||||||
|
```
|
||||||
**Symptom:** An exception with the message 'TypeToken type argument must not contain a type variable' is thrown
|
|
||||||
|
See also the [Android example](examples/android-proguard-example/README.md) for more information.
|
||||||
**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: 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.
|
||||||
Note: Earlier version of Gson unfortunately did not prevent capturing type variables, which caused many users to unwittingly write type-unsafe code.
|
|
||||||
|
## <a id="r8-abstract-class"></a> `JsonIOException`: 'Abstract classes can't be instantiated!' (R8)
|
||||||
**Solution:**
|
|
||||||
|
**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).
|
||||||
- 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`.
|
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. If you already wrote a custom adapter, but it is not used, see [this troubleshooting point](#custom-adapter-not-used).
|
||||||
- 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).
|
|
||||||
|
**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.
|
||||||
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**:
|
|
||||||
|
**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:
|
||||||
- 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.
|
```
|
||||||
|
# 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.
|
||||||
|
|
10
UserGuide.md
10
UserGuide.md
|
@ -366,7 +366,7 @@ class Event {
|
||||||
|
|
||||||
You can serialize the collection with Gson without doing anything specific: `toJson(collection)` would write out the desired output.
|
You can serialize the collection with Gson without doing anything specific: `toJson(collection)` would write out the desired output.
|
||||||
|
|
||||||
However, deserialization with `fromJson(json, Collection.class)` will not work since Gson has no way of knowing how to map the input to the types. Gson requires that you provide a genericised version of collection type in `fromJson()`. So, you have three options:
|
However, deserialization with `fromJson(json, Collection.class)` will not work since Gson has no way of knowing how to map the input to the types. Gson requires that you provide a genericized version of the collection type in `fromJson()`. So, you have three options:
|
||||||
|
|
||||||
1. Use Gson's parser API (low-level streaming parser or the DOM parser JsonParser) to parse the array elements and then use `Gson.fromJson()` on each of the array elements.This is the preferred approach. [Here is an example](extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java) that demonstrates how to do this.
|
1. Use Gson's parser API (low-level streaming parser or the DOM parser JsonParser) to parse the array elements and then use `Gson.fromJson()` on each of the array elements.This is the preferred approach. [Here is an example](extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java) that demonstrates how to do this.
|
||||||
|
|
||||||
|
@ -389,7 +389,7 @@ You can also find source code for some commonly used classes such as JodaTime at
|
||||||
|
|
||||||
### Custom Serialization and Deserialization
|
### Custom Serialization and Deserialization
|
||||||
|
|
||||||
Sometimes default representation is not what you want. This is often the case when dealing with library classes (DateTime, etc).
|
Sometimes the default representation is not what you want. This is often the case when dealing with library classes (DateTime, etc.).
|
||||||
Gson allows you to register your own custom serializers and deserializers. This is done by defining two parts:
|
Gson allows you to register your own custom serializers and deserializers. This is done by defining two parts:
|
||||||
|
|
||||||
* JSON Serializers: Need to define custom serialization for an object
|
* JSON Serializers: Need to define custom serialization for an object
|
||||||
|
@ -405,7 +405,9 @@ gson.registerTypeAdapter(MyType.class, new MyDeserializer());
|
||||||
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());
|
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());
|
||||||
```
|
```
|
||||||
|
|
||||||
`registerTypeAdapter` call checks if the type adapter implements more than one of these interfaces and register it for all of them.
|
`registerTypeAdapter` call checks
|
||||||
|
1. if the type adapter implements more than one of these interfaces, in that case it registers the adapter for all of them.
|
||||||
|
2. if the type adapter is for the Object class or JsonElement or any of its subclasses, in that case it throws IllegalArgumentException because overriding the built-in adapters for these types is not supported.
|
||||||
|
|
||||||
#### Writing a Serializer
|
#### Writing a Serializer
|
||||||
|
|
||||||
|
@ -741,7 +743,7 @@ In addition Gson's object model and data binding, you can use Gson to read from
|
||||||
|
|
||||||
## Issues in Designing Gson
|
## Issues in Designing Gson
|
||||||
|
|
||||||
See the [Gson design document](GsonDesignDocument.md "Gson design document") for a discussion of issues we faced while designing Gson. It also include a comparison of Gson with other Java libraries that can be used for JSON conversion.
|
See the [Gson design document](GsonDesignDocument.md "Gson design document") for a discussion of issues we faced while designing Gson. It also includes a comparison of Gson with other Java libraries that can be used for JSON conversion.
|
||||||
|
|
||||||
## Future Enhancements to Gson
|
## Future Enhancements to Gson
|
||||||
|
|
||||||
|
|
|
@ -41,13 +41,13 @@ public class GsonProguardExampleActivity extends Activity {
|
||||||
Cart cart = buildCart();
|
Cart cart = buildCart();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Gson.toJson() example: \n");
|
sb.append("Gson.toJson() example: \n");
|
||||||
sb.append(" Cart Object: ").append(cart).append("\n");
|
sb.append(" Cart Object: ").append(cart).append('\n');
|
||||||
sb.append(" Cart JSON: ").append(gson.toJson(cart)).append("\n");
|
sb.append(" Cart JSON: ").append(gson.toJson(cart)).append('\n');
|
||||||
sb.append("\n\nGson.fromJson() example: \n");
|
sb.append("\n\nGson.fromJson() example: \n");
|
||||||
String json = "{buyer:'Happy Camper',creditCard:'4111-1111-1111-1111',"
|
String json = "{buyer:'Happy Camper',creditCard:'4111-1111-1111-1111',"
|
||||||
+ "lineItems:[{name:'nails',priceInMicros:100000,quantity:100,currencyCode:'USD'}]}";
|
+ "lineItems:[{name:'nails',priceInMicros:100000,quantity:100,currencyCode:'USD'}]}";
|
||||||
sb.append("Cart JSON: ").append(json).append("\n");
|
sb.append("Cart JSON: ").append(json).append('\n');
|
||||||
sb.append("Cart Object: ").append(gson.fromJson(json, Cart.class)).append("\n");
|
sb.append("Cart Object: ").append(gson.fromJson(json, Cart.class)).append('\n');
|
||||||
tv.setText(sb.toString());
|
tv.setText(sb.toString());
|
||||||
tv.invalidate();
|
tv.invalidate();
|
||||||
}
|
}
|
||||||
|
|
22
gson/pom.xml
22
gson/pom.xml
|
@ -33,6 +33,12 @@
|
||||||
</license>
|
</license>
|
||||||
</licenses>
|
</licenses>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- Make the build reproducible, see root `pom.xml` -->
|
||||||
|
<!-- This is duplicated here because that is recommended by `artifact:check-buildplan` -->
|
||||||
|
<project.build.outputTimestamp>2023-01-01T00:00:00Z</project.build.outputTimestamp>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!--
|
<!--
|
||||||
errorprone was removed due to JPMS:
|
errorprone was removed due to JPMS:
|
||||||
|
@ -54,7 +60,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava-testlib</artifactId>
|
<artifactId>guava-testlib</artifactId>
|
||||||
<version>32.1.2-jre</version>
|
<version>33.0.0-jre</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -67,7 +73,7 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>templating-maven-plugin</artifactId>
|
<artifactId>templating-maven-plugin</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>3.0.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>filtering-java-templates</id>
|
<id>filtering-java-templates</id>
|
||||||
|
@ -135,11 +141,11 @@
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<!-- Deny illegal access, this is required for ReflectionAccessTest -->
|
<!-- Deny illegal access, this is required for ReflectionAccessTest -->
|
||||||
<!-- Requires Java >= 9; Important: In case future Java versions
|
<!-- Requires Java >= 9; Important: In case future Java versions
|
||||||
don't support this flag anymore, don't remove it unless CI also runs with
|
don't support this flag anymore, don't remove it unless CI also runs with
|
||||||
that Java version. Ideally would use toolchain to specify that this should
|
that Java version. Ideally would use toolchain to specify that this should
|
||||||
run with e.g. Java 11, but Maven toolchain requirements (unlike Gradle ones)
|
run with e.g. Java 11, but Maven toolchain requirements (unlike Gradle ones)
|
||||||
don't seem to be portable (every developer would have to set up toolchain
|
don't seem to be portable (every developer would have to set up toolchain
|
||||||
configuration locally). -->
|
configuration locally). -->
|
||||||
<argLine>--illegal-access=deny</argLine>
|
<argLine>--illegal-access=deny</argLine>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -189,7 +195,7 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.moditect</groupId>
|
<groupId>org.moditect</groupId>
|
||||||
<artifactId>moditect-maven-plugin</artifactId>
|
<artifactId>moditect-maven-plugin</artifactId>
|
||||||
<version>1.0.0.RC2</version>
|
<version>1.1.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>add-module-info</id>
|
<id>add-module-info</id>
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
package com.google.gson.internal;
|
package com.google.gson.internal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build configuration for Gson. This file is automatically populated by
|
* Build configuration for Gson. This file is automatically populated by templating-maven-plugin and
|
||||||
* templating-maven-plugin and .java/.class files are generated for use in Gson.
|
* .java/.class files are generated for use in Gson.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
*/
|
*/
|
||||||
|
@ -28,5 +28,5 @@ public final class GsonBuildConfig {
|
||||||
/** This field is automatically populated by Maven when a build is triggered */
|
/** This field is automatically populated by Maven when a build is triggered */
|
||||||
public static final String VERSION = "${project.version}";
|
public static final String VERSION = "${project.version}";
|
||||||
|
|
||||||
private GsonBuildConfig() { }
|
private GsonBuildConfig() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,13 @@
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A strategy (or policy) definition that is used to decide whether or not a field or
|
* A strategy (or policy) definition that is used to decide whether or not a field or class should
|
||||||
* class should be serialized or deserialized as part of the JSON output/input.
|
* be serialized or deserialized as part of the JSON output/input.
|
||||||
*
|
*
|
||||||
* <p>The following are a few examples that shows how you can use this exclusion mechanism.
|
* <p>The following are a few examples that shows how you can use this exclusion mechanism.
|
||||||
*
|
*
|
||||||
* <p><strong>Exclude fields and objects based on a particular class type:</strong>
|
* <p><strong>Exclude fields and objects based on a particular class type:</strong>
|
||||||
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
|
* private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
|
||||||
* private final Class<?> excludedThisClass;
|
* private final Class<?> excludedThisClass;
|
||||||
|
@ -42,6 +43,7 @@ package com.google.gson;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p><strong>Excludes fields and objects based on a particular annotation:</strong>
|
* <p><strong>Excludes fields and objects based on a particular annotation:</strong>
|
||||||
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* public @interface FooAnnotation {
|
* public @interface FooAnnotation {
|
||||||
* // some implementation here
|
* // some implementation here
|
||||||
|
@ -59,9 +61,10 @@ package com.google.gson;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then
|
* <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then the
|
||||||
* the {@code GsonBuilder} is required. The following is an example of how you can use the
|
* {@code GsonBuilder} is required. The following is an example of how you can use the {@code
|
||||||
* {@code GsonBuilder} to configure Gson to use one of the above samples:
|
* GsonBuilder} to configure Gson to use one of the above samples:
|
||||||
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
|
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
|
||||||
* Gson gson = new GsonBuilder()
|
* Gson gson = new GsonBuilder()
|
||||||
|
@ -70,10 +73,10 @@ package com.google.gson;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>For certain model classes, you may only want to serialize a field, but exclude it for
|
* <p>For certain model classes, you may only want to serialize a field, but exclude it for
|
||||||
* deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal;
|
* deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal; however,
|
||||||
* however, you would register it with the
|
* you would register it with the {@link
|
||||||
* {@link GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method.
|
* GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method. For example:
|
||||||
* For example:
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
|
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
|
||||||
* Gson gson = new GsonBuilder()
|
* Gson gson = new GsonBuilder()
|
||||||
|
@ -83,11 +86,9 @@ package com.google.gson;
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*
|
|
||||||
* @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...)
|
* @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...)
|
||||||
* @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)
|
* @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)
|
||||||
* @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy)
|
* @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy)
|
||||||
*
|
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
public interface ExclusionStrategy {
|
public interface ExclusionStrategy {
|
||||||
|
|
|
@ -30,7 +30,6 @@ import java.util.Objects;
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*
|
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
public final class FieldAttributes {
|
public final class FieldAttributes {
|
||||||
|
@ -64,7 +63,10 @@ public final class FieldAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Returns the declared generic type of the field.
|
||||||
|
*
|
||||||
* <p>For example, assume the following class definition:
|
* <p>For example, assume the following class definition:
|
||||||
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* public class Foo {
|
* public class Foo {
|
||||||
* private String bar;
|
* private String bar;
|
||||||
|
@ -74,8 +76,8 @@ public final class FieldAttributes {
|
||||||
* Type listParameterizedType = new TypeToken<List<String>>() {}.getType();
|
* Type listParameterizedType = new TypeToken<List<String>>() {}.getType();
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>This method would return {@code String.class} for the {@code bar} field and
|
* <p>This method would return {@code String.class} for the {@code bar} field and {@code
|
||||||
* {@code listParameterizedType} for the {@code red} field.
|
* listParameterizedType} for the {@code red} field.
|
||||||
*
|
*
|
||||||
* @return the specific type declared for this field
|
* @return the specific type declared for this field
|
||||||
*/
|
*/
|
||||||
|
@ -87,6 +89,7 @@ public final class FieldAttributes {
|
||||||
* Returns the {@code Class} object that was declared for this field.
|
* Returns the {@code Class} object that was declared for this field.
|
||||||
*
|
*
|
||||||
* <p>For example, assume the following class definition:
|
* <p>For example, assume the following class definition:
|
||||||
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* public class Foo {
|
* public class Foo {
|
||||||
* private String bar;
|
* private String bar;
|
||||||
|
@ -94,8 +97,8 @@ public final class FieldAttributes {
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>This method would return {@code String.class} for the {@code bar} field and
|
* <p>This method would return {@code String.class} for the {@code bar} field and {@code
|
||||||
* {@code List.class} for the {@code red} field.
|
* List.class} for the {@code red} field.
|
||||||
*
|
*
|
||||||
* @return the specific class object that was declared for the field
|
* @return the specific class object that was declared for the field
|
||||||
*/
|
*/
|
||||||
|
@ -104,8 +107,8 @@ public final class FieldAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the {@code T} annotation object from this field if it exist; otherwise returns
|
* Returns the {@code T} annotation object from this field if it exists; otherwise returns {@code
|
||||||
* {@code null}.
|
* null}.
|
||||||
*
|
*
|
||||||
* @param annotation the class of the annotation that will be retrieved
|
* @param annotation the class of the annotation that will be retrieved
|
||||||
* @return the annotation instance if it is bound to the field; otherwise {@code null}
|
* @return the annotation instance if it is bound to the field; otherwise {@code null}
|
||||||
|
@ -115,7 +118,7 @@ public final class FieldAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the annotations that are present on this field.
|
* Returns the annotations that are present on this field.
|
||||||
*
|
*
|
||||||
* @return an array of all the annotations set on the field
|
* @return an array of all the annotations set on the field
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
|
@ -128,6 +131,7 @@ public final class FieldAttributes {
|
||||||
* Returns {@code true} if the field is defined with the {@code modifier}.
|
* Returns {@code true} if the field is defined with the {@code modifier}.
|
||||||
*
|
*
|
||||||
* <p>This method is meant to be called as:
|
* <p>This method is meant to be called as:
|
||||||
|
*
|
||||||
* <pre class="code">
|
* <pre class="code">
|
||||||
* boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
|
* boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
|
||||||
* </pre>
|
* </pre>
|
||||||
|
|
|
@ -20,150 +20,161 @@ import java.lang.reflect.Field;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enumeration that defines a few standard naming conventions for JSON field names.
|
* An enumeration that defines a few standard naming conventions for JSON field names. This
|
||||||
* This enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder}
|
* enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder} to configure a
|
||||||
* to configure a {@link com.google.gson.Gson} instance to properly translate Java field
|
* {@link com.google.gson.Gson} instance to properly translate Java field names into the desired
|
||||||
* names into the desired JSON field names.
|
* JSON field names.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
public enum FieldNamingPolicy implements FieldNamingStrategy {
|
public enum FieldNamingPolicy implements FieldNamingStrategy {
|
||||||
|
|
||||||
/**
|
/** Using this naming policy with Gson will ensure that the field name is unchanged. */
|
||||||
* Using this naming policy with Gson will ensure that the field name is
|
|
||||||
* unchanged.
|
|
||||||
*/
|
|
||||||
IDENTITY() {
|
IDENTITY() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return f.getName();
|
return f.getName();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this naming policy with Gson will ensure that the first "letter" of the Java
|
* Using this naming policy with Gson will ensure that the first "letter" of the Java field name
|
||||||
* field name is capitalized when serialized to its JSON form.
|
* is capitalized when serialized to its JSON form.
|
||||||
|
*
|
||||||
|
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> SomeFieldName</li>
|
* <li>someFieldName ---> SomeFieldName
|
||||||
* <li>_someFieldName ---> _SomeFieldName</li>
|
* <li>_someFieldName ---> _SomeFieldName
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
UPPER_CAMEL_CASE() {
|
UPPER_CAMEL_CASE() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return upperCaseFirstLetter(f.getName());
|
return upperCaseFirstLetter(f.getName());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this naming policy with Gson will ensure that the first "letter" of the Java
|
* Using this naming policy with Gson will ensure that the first "letter" of the Java field name
|
||||||
* field name is capitalized when serialized to its JSON form and the words will be
|
* is capitalized when serialized to its JSON form and the words will be separated by a space.
|
||||||
* separated by a space.
|
*
|
||||||
|
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> Some Field Name</li>
|
* <li>someFieldName ---> Some Field Name
|
||||||
* <li>_someFieldName ---> _Some Field Name</li>
|
* <li>_someFieldName ---> _Some Field Name
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
UPPER_CAMEL_CASE_WITH_SPACES() {
|
UPPER_CAMEL_CASE_WITH_SPACES() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return upperCaseFirstLetter(separateCamelCase(f.getName(), ' '));
|
return upperCaseFirstLetter(separateCamelCase(f.getName(), ' '));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this naming policy with Gson will modify the Java Field name from its camel cased
|
* Using this naming policy with Gson will modify the Java Field name from its camel cased form to
|
||||||
* form to an upper case field name where each word is separated by an underscore (_).
|
* an upper case field name where each word is separated by an underscore (_).
|
||||||
|
*
|
||||||
|
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> SOME_FIELD_NAME</li>
|
* <li>someFieldName ---> SOME_FIELD_NAME
|
||||||
* <li>_someFieldName ---> _SOME_FIELD_NAME</li>
|
* <li>_someFieldName ---> _SOME_FIELD_NAME
|
||||||
* <li>aStringField ---> A_STRING_FIELD</li>
|
* <li>aStringField ---> A_STRING_FIELD
|
||||||
* <li>aURL ---> A_U_R_L</li>
|
* <li>aURL ---> A_U_R_L
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @since 2.9.0
|
* @since 2.9.0
|
||||||
*/
|
*/
|
||||||
UPPER_CASE_WITH_UNDERSCORES() {
|
UPPER_CASE_WITH_UNDERSCORES() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return separateCamelCase(f.getName(), '_').toUpperCase(Locale.ENGLISH);
|
return separateCamelCase(f.getName(), '_').toUpperCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this naming policy with Gson will modify the Java Field name from its camel cased
|
* Using this naming policy with Gson will modify the Java Field name from its camel cased form to
|
||||||
* form to a lower case field name where each word is separated by an underscore (_).
|
* a lower case field name where each word is separated by an underscore (_).
|
||||||
|
*
|
||||||
|
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> some_field_name</li>
|
* <li>someFieldName ---> some_field_name
|
||||||
* <li>_someFieldName ---> _some_field_name</li>
|
* <li>_someFieldName ---> _some_field_name
|
||||||
* <li>aStringField ---> a_string_field</li>
|
* <li>aStringField ---> a_string_field
|
||||||
* <li>aURL ---> a_u_r_l</li>
|
* <li>aURL ---> a_u_r_l
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
LOWER_CASE_WITH_UNDERSCORES() {
|
LOWER_CASE_WITH_UNDERSCORES() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return separateCamelCase(f.getName(), '_').toLowerCase(Locale.ENGLISH);
|
return separateCamelCase(f.getName(), '_').toLowerCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this naming policy with Gson will modify the Java Field name from its camel cased
|
* Using this naming policy with Gson will modify the Java Field name from its camel cased form to
|
||||||
* form to a lower case field name where each word is separated by a dash (-).
|
* a lower case field name where each word is separated by a dash (-).
|
||||||
|
*
|
||||||
|
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> some-field-name</li>
|
* <li>someFieldName ---> some-field-name
|
||||||
* <li>_someFieldName ---> _some-field-name</li>
|
* <li>_someFieldName ---> _some-field-name
|
||||||
* <li>aStringField ---> a-string-field</li>
|
* <li>aStringField ---> a-string-field
|
||||||
* <li>aURL ---> a-u-r-l</li>
|
* <li>aURL ---> a-u-r-l
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
* Using dashes in JavaScript is not recommended since dash is also used for a minus sign in
|
* Using dashes in JavaScript is not recommended since dash is also used for a minus sign in
|
||||||
* expressions. This requires that a field named with dashes is always accessed as a quoted
|
* expressions. This requires that a field named with dashes is always accessed as a quoted
|
||||||
* property like {@code myobject['my-field']}. Accessing it as an object field
|
* property like {@code myobject['my-field']}. Accessing it as an object field {@code
|
||||||
* {@code myobject.my-field} will result in an unintended JavaScript expression.
|
* myobject.my-field} will result in an unintended JavaScript expression.
|
||||||
*
|
*
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
LOWER_CASE_WITH_DASHES() {
|
LOWER_CASE_WITH_DASHES() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return separateCamelCase(f.getName(), '-').toLowerCase(Locale.ENGLISH);
|
return separateCamelCase(f.getName(), '-').toLowerCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this naming policy with Gson will modify the Java Field name from its camel cased
|
* Using this naming policy with Gson will modify the Java Field name from its camel cased form to
|
||||||
* form to a lower case field name where each word is separated by a dot (.).
|
* a lower case field name where each word is separated by a dot (.).
|
||||||
|
*
|
||||||
|
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":
|
||||||
*
|
*
|
||||||
* <p>Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>someFieldName ---> some.field.name</li>
|
* <li>someFieldName ---> some.field.name
|
||||||
* <li>_someFieldName ---> _some.field.name</li>
|
* <li>_someFieldName ---> _some.field.name
|
||||||
* <li>aStringField ---> a.string.field</li>
|
* <li>aStringField ---> a.string.field
|
||||||
* <li>aURL ---> a.u.r.l</li>
|
* <li>aURL ---> a.u.r.l
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
* Using dots in JavaScript is not recommended since dot is also used for a member sign in
|
* Using dots in JavaScript is not recommended since dot is also used for a member sign in
|
||||||
* expressions. This requires that a field named with dots is always accessed as a quoted
|
* expressions. This requires that a field named with dots is always accessed as a quoted property
|
||||||
* property like {@code myobject['my.field']}. Accessing it as an object field
|
* like {@code myobject['my.field']}. Accessing it as an object field {@code myobject.my.field}
|
||||||
* {@code myobject.my.field} will result in an unintended JavaScript expression.
|
* will result in an unintended JavaScript expression.
|
||||||
*
|
*
|
||||||
* @since 2.8.4
|
* @since 2.8.4
|
||||||
*/
|
*/
|
||||||
LOWER_CASE_WITH_DOTS() {
|
LOWER_CASE_WITH_DOTS() {
|
||||||
@Override public String translateName(Field f) {
|
@Override
|
||||||
|
public String translateName(Field f) {
|
||||||
return separateCamelCase(f.getName(), '.').toLowerCase(Locale.ENGLISH);
|
return separateCamelCase(f.getName(), '.').toLowerCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the field name that uses camel-case define word separation into
|
* Converts the field name that uses camel-case define word separation into separate words that
|
||||||
* separate words that are separated by the provided {@code separator}.
|
* are separated by the provided {@code separator}.
|
||||||
*/
|
*/
|
||||||
static String separateCamelCase(String name, char separator) {
|
static String separateCamelCase(String name, char separator) {
|
||||||
StringBuilder translation = new StringBuilder();
|
StringBuilder translation = new StringBuilder();
|
||||||
|
@ -177,9 +188,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy {
|
||||||
return translation.toString();
|
return translation.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Ensures the JSON field names begins with an upper case letter. */
|
||||||
* Ensures the JSON field names begins with an upper case letter.
|
|
||||||
*/
|
|
||||||
static String upperCaseFirstLetter(String s) {
|
static String upperCaseFirstLetter(String s) {
|
||||||
int length = s.length();
|
int length = s.length();
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
|
|
@ -20,8 +20,8 @@ import java.lang.reflect.Field;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mechanism for providing custom field naming in Gson. This allows the client code to translate
|
* A mechanism for providing custom field naming in Gson. This allows the client code to translate
|
||||||
* field names into a particular convention that is not supported as a normal Java field
|
* field names into a particular convention that is not supported as a normal Java field declaration
|
||||||
* declaration rules. For example, Java does not support "-" characters in a field name.
|
* rules. For example, Java does not support "-" characters in a field name.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
|
|
@ -22,8 +22,9 @@ import java.util.Objects;
|
||||||
/**
|
/**
|
||||||
* A class used to control what the serialization output looks like.
|
* A class used to control what the serialization output looks like.
|
||||||
*
|
*
|
||||||
* <p>It currently has the following configuration methods, but more methods
|
* <p>It currently has the following configuration methods, but more methods might be added in the
|
||||||
* might be added in the future:
|
* future:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #withNewline(String)}
|
* <li>{@link #withNewline(String)}
|
||||||
* <li>{@link #withIndent(String)}
|
* <li>{@link #withIndent(String)}
|
||||||
|
@ -33,7 +34,6 @@ import java.util.Objects;
|
||||||
* @see GsonBuilder#setFormattingStyle(FormattingStyle)
|
* @see GsonBuilder#setFormattingStyle(FormattingStyle)
|
||||||
* @see JsonWriter#setFormattingStyle(FormattingStyle)
|
* @see JsonWriter#setFormattingStyle(FormattingStyle)
|
||||||
* @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a>
|
* @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a>
|
||||||
*
|
|
||||||
* @since $next-version$
|
* @since $next-version$
|
||||||
*/
|
*/
|
||||||
public class FormattingStyle {
|
public class FormattingStyle {
|
||||||
|
@ -43,6 +43,7 @@ public class FormattingStyle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default compact formatting style:
|
* The default compact formatting style:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>no newline
|
* <li>no newline
|
||||||
* <li>no indent
|
* <li>no indent
|
||||||
|
@ -53,14 +54,14 @@ public class FormattingStyle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default pretty printing formatting style:
|
* The default pretty printing formatting style:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@code "\n"} as newline
|
* <li>{@code "\n"} as newline
|
||||||
* <li>two spaces as indent
|
* <li>two spaces as indent
|
||||||
* <li>a space between {@code ':'} and the subsequent value
|
* <li>a space between {@code ':'} and the subsequent value
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public static final FormattingStyle PRETTY =
|
public static final FormattingStyle PRETTY = new FormattingStyle("\n", " ", true);
|
||||||
new FormattingStyle("\n", " ", true);
|
|
||||||
|
|
||||||
private FormattingStyle(String newline, String indent, boolean spaceAfterSeparators) {
|
private FormattingStyle(String newline, String indent, boolean spaceAfterSeparators) {
|
||||||
Objects.requireNonNull(newline, "newline == null");
|
Objects.requireNonNull(newline, "newline == null");
|
||||||
|
@ -81,11 +82,11 @@ public class FormattingStyle {
|
||||||
/**
|
/**
|
||||||
* Creates a {@link FormattingStyle} with the specified newline setting.
|
* Creates a {@link FormattingStyle} with the specified newline setting.
|
||||||
*
|
*
|
||||||
* <p>It can be used to accommodate certain OS convention, for example
|
* <p>It can be used to accommodate certain OS convention, for example hardcode {@code "\n"} for
|
||||||
* hardcode {@code "\n"} for Linux and macOS, {@code "\r\n"} for Windows, or
|
* Linux and macOS, {@code "\r\n"} for Windows, or call {@link java.lang.System#lineSeparator()}
|
||||||
* call {@link java.lang.System#lineSeparator()} to match the current OS.</p>
|
* to match the current OS.
|
||||||
*
|
*
|
||||||
* <p>Only combinations of {@code \n} and {@code \r} are allowed.</p>
|
* <p>Only combinations of {@code \n} and {@code \r} are allowed.
|
||||||
*
|
*
|
||||||
* @param newline the string value that will be used as newline.
|
* @param newline the string value that will be used as newline.
|
||||||
* @return a newly created {@link FormattingStyle}
|
* @return a newly created {@link FormattingStyle}
|
||||||
|
@ -97,7 +98,7 @@ public class FormattingStyle {
|
||||||
/**
|
/**
|
||||||
* Creates a {@link FormattingStyle} with the specified indent string.
|
* Creates a {@link FormattingStyle} with the specified indent string.
|
||||||
*
|
*
|
||||||
* <p>Only combinations of spaces and tabs allowed in indent.</p>
|
* <p>Only combinations of spaces and tabs allowed in indent.
|
||||||
*
|
*
|
||||||
* @param indent the string value that will be used as indent.
|
* @param indent the string value that will be used as indent.
|
||||||
* @return a newly created {@link FormattingStyle}
|
* @return a newly created {@link FormattingStyle}
|
||||||
|
@ -107,12 +108,12 @@ public class FormattingStyle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link FormattingStyle} which either uses a space after
|
* Creates a {@link FormattingStyle} which either uses a space after the separators {@code ','}
|
||||||
* the separators {@code ','} and {@code ':'} in the JSON output, or not.
|
* and {@code ':'} in the JSON output, or not.
|
||||||
*
|
*
|
||||||
* <p>This setting has no effect on the {@linkplain #withNewline(String) configured newline}.
|
* <p>This setting has no effect on the {@linkplain #withNewline(String) configured newline}. If a
|
||||||
* If a non-empty newline is configured, it will always be added after
|
* non-empty newline is configured, it will always be added after {@code ','} and no space is
|
||||||
* {@code ','} and no space is added after the {@code ','} in that case.</p>
|
* added after the {@code ','} in that case.
|
||||||
*
|
*
|
||||||
* @param spaceAfterSeparators whether to output a space after {@code ','} and {@code ':'}.
|
* @param spaceAfterSeparators whether to output a space after {@code ','} and {@code ':'}.
|
||||||
* @return a newly created {@link FormattingStyle}
|
* @return a newly created {@link FormattingStyle}
|
||||||
|
@ -139,9 +140,7 @@ public class FormattingStyle {
|
||||||
return this.indent;
|
return this.indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns whether a space will be used after {@code ','} and {@code ':'}. */
|
||||||
* Returns whether a space will be used after {@code ','} and {@code ':'}.
|
|
||||||
*/
|
|
||||||
public boolean usesSpaceAfterSeparators() {
|
public boolean usesSpaceAfterSeparators() {
|
||||||
return this.spaceAfterSeparators;
|
return this.spaceAfterSeparators;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -31,6 +31,7 @@ import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -41,10 +42,10 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
|
* Use this builder to construct a {@link Gson} instance when you need to set configuration options
|
||||||
* options other than the default. For {@link Gson} with default configuration, it is simpler to
|
* other than the default. For {@link Gson} with default configuration, it is simpler to use {@code
|
||||||
* use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its
|
* new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its various
|
||||||
* various configuration methods, and finally calling create.</p>
|
* configuration methods, and finally calling create.
|
||||||
*
|
*
|
||||||
* <p>The following 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:
|
||||||
*
|
*
|
||||||
|
@ -53,7 +54,7 @@ import java.util.Objects;
|
||||||
* .registerTypeAdapter(Id.class, new IdTypeAdapter())
|
* .registerTypeAdapter(Id.class, new IdTypeAdapter())
|
||||||
* .enableComplexMapKeySerialization()
|
* .enableComplexMapKeySerialization()
|
||||||
* .serializeNulls()
|
* .serializeNulls()
|
||||||
* .setDateFormat(DateFormat.LONG)
|
* .setDateFormat(DateFormat.LONG, DateFormat.LONG)
|
||||||
* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
|
* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
|
||||||
* .setPrettyPrinting()
|
* .setPrettyPrinting()
|
||||||
* .setVersion(1.0)
|
* .setVersion(1.0)
|
||||||
|
@ -61,15 +62,16 @@ import java.util.Objects;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>Notes:
|
* <p>Notes:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The order of invocation of configuration methods does not matter.</li>
|
* <li>The order of invocation of configuration methods does not matter.
|
||||||
* <li>The default serialization of {@link Date} and its subclasses in Gson does
|
* <li>The default serialization of {@link Date} and its subclasses in Gson does not contain
|
||||||
* not contain time-zone information. So, if you are using date/time instances,
|
* time-zone information. So, if you are using date/time instances, use {@code GsonBuilder}
|
||||||
* use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
|
* and its {@code setDateFormat} methods.
|
||||||
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods
|
* <li>By default no explicit {@link Strictness} is set; some of the {@link Gson} methods behave
|
||||||
* behave as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as
|
* as if {@link Strictness#LEGACY_STRICT} was used whereas others behave as if {@link
|
||||||
* if {@link Strictness#LENIENT} was used. Prefer explicitly setting a strictness
|
* Strictness#LENIENT} was used. Prefer explicitly setting a strictness with {@link
|
||||||
* with {@link #setStrictness(Strictness)} to avoid this legacy behavior.
|
* #setStrictness(Strictness)} to avoid this legacy behavior.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
|
@ -82,8 +84,10 @@ public final class GsonBuilder {
|
||||||
private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
|
private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
|
||||||
private final Map<Type, InstanceCreator<?>> instanceCreators = new HashMap<>();
|
private final Map<Type, InstanceCreator<?>> instanceCreators = new HashMap<>();
|
||||||
private final List<TypeAdapterFactory> factories = new ArrayList<>();
|
private final List<TypeAdapterFactory> factories = new ArrayList<>();
|
||||||
|
|
||||||
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */
|
/** tree-style hierarchy factories. These come after factories for backwards compatibility. */
|
||||||
private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<>();
|
private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<>();
|
||||||
|
|
||||||
private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS;
|
private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS;
|
||||||
private String datePattern = DEFAULT_DATE_PATTERN;
|
private String datePattern = DEFAULT_DATE_PATTERN;
|
||||||
private int dateStyle = DateFormat.DEFAULT;
|
private int dateStyle = DateFormat.DEFAULT;
|
||||||
|
@ -103,16 +107,14 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
|
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
|
||||||
* settings. GsonBuilder follows the builder pattern, and it is typically used by first
|
* settings. GsonBuilder follows the builder pattern, and it is typically used by first invoking
|
||||||
* invoking various configuration methods to set desired options, and finally calling
|
* various configuration methods to set desired options, and finally calling {@link #create()}.
|
||||||
* {@link #create()}.
|
|
||||||
*/
|
*/
|
||||||
public GsonBuilder() {
|
public GsonBuilder() {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder
|
* Constructs a GsonBuilder instance from a Gson instance. The newly constructed GsonBuilder has
|
||||||
* has the same configuration as the previously built Gson instance.
|
* the same configuration as the previously built Gson instance.
|
||||||
*
|
*
|
||||||
* @param gson the gson instance whose configuration should be applied to a new GsonBuilder.
|
* @param gson the gson instance whose configuration should be applied to a new GsonBuilder.
|
||||||
*/
|
*/
|
||||||
|
@ -142,13 +144,13 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to enable versioning support. Versioning support works based on the
|
* Configures Gson to enable versioning support. Versioning support works based on the annotation
|
||||||
* annotation types {@link Since} and {@link Until}. It allows including or excluding fields
|
* types {@link Since} and {@link Until}. It allows including or excluding fields and classes
|
||||||
* and classes based on the specified version. See the documentation of these annotation
|
* based on the specified version. See the documentation of these annotation types for more
|
||||||
* types for more information.
|
* information.
|
||||||
*
|
*
|
||||||
* <p>By default versioning support is disabled and usage of {@code @Since} and {@code @Until}
|
* <p>By default versioning support is disabled and usage of {@code @Since} and {@code @Until} has
|
||||||
* has no effect.
|
* no effect.
|
||||||
*
|
*
|
||||||
* @param version the version number to use.
|
* @param version the version number to use.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -169,13 +171,13 @@ public final class GsonBuilder {
|
||||||
* Gson will exclude all fields marked {@code transient} or {@code static}. This method will
|
* Gson will exclude all fields marked {@code transient} or {@code static}. This method will
|
||||||
* override that behavior.
|
* override that behavior.
|
||||||
*
|
*
|
||||||
* <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which
|
* <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which excludes
|
||||||
* excludes these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}.
|
* these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this
|
||||||
|
* builder}.
|
||||||
*
|
*
|
||||||
* @param modifiers the field modifiers. You must use the modifiers specified in the
|
* @param modifiers the field modifiers. You must use the modifiers specified in the {@link
|
||||||
* {@link java.lang.reflect.Modifier} class. For example,
|
* java.lang.reflect.Modifier} class. For example, {@link
|
||||||
* {@link java.lang.reflect.Modifier#TRANSIENT},
|
* java.lang.reflect.Modifier#TRANSIENT}, {@link java.lang.reflect.Modifier#STATIC}.
|
||||||
* {@link java.lang.reflect.Modifier#STATIC}.
|
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
*/
|
*/
|
||||||
public GsonBuilder excludeFieldsWithModifiers(int... modifiers) {
|
public GsonBuilder excludeFieldsWithModifiers(int... modifiers) {
|
||||||
|
@ -186,9 +188,8 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some
|
* Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some
|
||||||
* special text. This prevents attacks from third-party sites through script sourcing. See
|
* special text. This prevents attacks from third-party sites through script sourcing. See <a
|
||||||
* <a href="http://code.google.com/p/google-gson/issues/detail?id=42">Gson Issue 42</a>
|
* href="http://code.google.com/p/google-gson/issues/detail?id=42">Gson Issue 42</a> for details.
|
||||||
* for details.
|
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
|
@ -203,7 +204,8 @@ public final class GsonBuilder {
|
||||||
* that do not have the {@link com.google.gson.annotations.Expose} annotation.
|
* that do not have the {@link com.google.gson.annotations.Expose} annotation.
|
||||||
*
|
*
|
||||||
* <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which excludes
|
* <p>This is a convenience method which behaves as if an {@link ExclusionStrategy} which excludes
|
||||||
* these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}.
|
* these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this
|
||||||
|
* builder}.
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
*/
|
*/
|
||||||
|
@ -213,7 +215,7 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure Gson to serialize null fields. By default, Gson omits all fields that are null
|
* Configures Gson to serialize null fields. By default, Gson omits all fields that are null
|
||||||
* during serialization.
|
* during serialization.
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -225,79 +227,82 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enabling this feature will only change the serialized form if the map key is
|
* Configures Gson to serialize {@code Map} objects with complex keys as JSON arrays. Enabling
|
||||||
* a complex type (i.e. non-primitive) in its <strong>serialized</strong> JSON
|
* this feature will only change the serialized form if the map key is a complex type (i.e.
|
||||||
* form. The default implementation of map serialization uses {@code toString()}
|
* non-primitive) in its <strong>serialized</strong> JSON form. The default implementation of map
|
||||||
* on the key; however, when this is called then one of the following cases
|
* serialization uses {@code toString()} on the key; however, when this is called then one of the
|
||||||
* apply:
|
* following cases apply:
|
||||||
*
|
*
|
||||||
* <p><b>Maps as JSON objects</b>
|
* <p><b>Maps as JSON objects</b>
|
||||||
*
|
*
|
||||||
* <p>For this case, assume that a type adapter is registered to serialize and
|
* <p>For this case, assume that a type adapter is registered to serialize and deserialize some
|
||||||
* deserialize some {@code Point} class, which contains an x and y coordinate,
|
* {@code Point} class, which contains an x and y coordinate, to/from the JSON Primitive string
|
||||||
* to/from the JSON Primitive string value {@code "(x,y)"}. The Java map would
|
* value {@code "(x,y)"}. The Java map would then be serialized as a {@link JsonObject}.
|
||||||
* then be serialized as a {@link JsonObject}.
|
|
||||||
*
|
*
|
||||||
* <p>Below is an example:
|
* <p>Below is an example:
|
||||||
* <pre> {@code
|
|
||||||
* Gson gson = new GsonBuilder()
|
|
||||||
* .register(Point.class, new MyPointTypeAdapter())
|
|
||||||
* .enableComplexMapKeySerialization()
|
|
||||||
* .create();
|
|
||||||
*
|
*
|
||||||
* Map<Point, String> original = new LinkedHashMap<>();
|
* <pre>{@code
|
||||||
* original.put(new Point(5, 6), "a");
|
* Gson gson = new GsonBuilder()
|
||||||
* original.put(new Point(8, 8), "b");
|
* .register(Point.class, new MyPointTypeAdapter())
|
||||||
* System.out.println(gson.toJson(original, type));
|
* .enableComplexMapKeySerialization()
|
||||||
|
* .create();
|
||||||
|
*
|
||||||
|
* Map<Point, String> original = new LinkedHashMap<>();
|
||||||
|
* original.put(new Point(5, 6), "a");
|
||||||
|
* original.put(new Point(8, 8), "b");
|
||||||
|
* System.out.println(gson.toJson(original, type));
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* The above code prints this JSON object:<pre> {@code
|
*
|
||||||
* {
|
* The above code prints this JSON object:
|
||||||
* "(5,6)": "a",
|
*
|
||||||
* "(8,8)": "b"
|
* <pre>{@code
|
||||||
* }
|
* {
|
||||||
|
* "(5,6)": "a",
|
||||||
|
* "(8,8)": "b"
|
||||||
|
* }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p><b>Maps as JSON arrays</b>
|
* <p><b>Maps as JSON arrays</b>
|
||||||
*
|
*
|
||||||
* <p>For this case, assume that a type adapter was NOT registered for some
|
* <p>For this case, assume that a type adapter was NOT registered for some {@code Point} class,
|
||||||
* {@code Point} class, but rather the default Gson serialization is applied.
|
* but rather the default Gson serialization is applied. In this case, some {@code new Point(2,3)}
|
||||||
* In this case, some {@code new Point(2,3)} would serialize as {@code
|
* would serialize as {@code {"x":2,"y":3}}.
|
||||||
* {"x":2,"y":3}}.
|
|
||||||
*
|
*
|
||||||
* <p>Given the assumption above, a {@code Map<Point, String>} will be
|
* <p>Given the assumption above, a {@code Map<Point, String>} will be serialized as an array of
|
||||||
* serialized as an array of arrays (can be viewed as an entry set of pairs).
|
* arrays (can be viewed as an entry set of pairs).
|
||||||
*
|
*
|
||||||
* <p>Below is an example of serializing complex types as JSON arrays:
|
* <p>Below is an example of serializing complex types as JSON arrays:
|
||||||
* <pre> {@code
|
|
||||||
* Gson gson = new GsonBuilder()
|
|
||||||
* .enableComplexMapKeySerialization()
|
|
||||||
* .create();
|
|
||||||
*
|
*
|
||||||
* Map<Point, String> original = new LinkedHashMap<>();
|
* <pre>{@code
|
||||||
* original.put(new Point(5, 6), "a");
|
* Gson gson = new GsonBuilder()
|
||||||
* original.put(new Point(8, 8), "b");
|
* .enableComplexMapKeySerialization()
|
||||||
* System.out.println(gson.toJson(original, type));
|
* .create();
|
||||||
* }
|
*
|
||||||
* </pre>
|
* Map<Point, String> original = new LinkedHashMap<>();
|
||||||
|
* original.put(new Point(5, 6), "a");
|
||||||
|
* original.put(new Point(8, 8), "b");
|
||||||
|
* System.out.println(gson.toJson(original, type));
|
||||||
|
* }</pre>
|
||||||
*
|
*
|
||||||
* The JSON output would look as follows:
|
* The JSON output would look as follows:
|
||||||
* <pre> {@code
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* [
|
||||||
* [
|
* [
|
||||||
* [
|
* {
|
||||||
* {
|
* "x": 5,
|
||||||
* "x": 5,
|
* "y": 6
|
||||||
* "y": 6
|
* },
|
||||||
* },
|
* "a"
|
||||||
* "a"
|
* ],
|
||||||
* ],
|
* [
|
||||||
* [
|
* {
|
||||||
* {
|
* "x": 8,
|
||||||
* "x": 8,
|
* "y": 8
|
||||||
* "y": 8
|
* },
|
||||||
* },
|
* "b"
|
||||||
* "b"
|
|
||||||
* ]
|
|
||||||
* ]
|
* ]
|
||||||
|
* ]
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -325,20 +330,22 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to exclude inner classes (= non-{@code static} nested classes) during serialization
|
* Configures Gson to exclude inner classes (= non-{@code static} nested classes) during
|
||||||
* and deserialization. This is a convenience method which behaves as if an {@link ExclusionStrategy}
|
* serialization and deserialization. This is a convenience method which behaves as if an {@link
|
||||||
* which excludes inner classes was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}.
|
* ExclusionStrategy} which excludes inner classes was {@linkplain
|
||||||
* This means inner classes will be serialized as JSON {@code null}, and will be deserialized as
|
* #setExclusionStrategies(ExclusionStrategy...) registered with this builder}. This means inner
|
||||||
* Java {@code null} with their JSON data being ignored. And fields with an inner class as type will
|
* classes will be serialized as JSON {@code null}, and will be deserialized as Java {@code null}
|
||||||
* be ignored during serialization and deserialization.
|
* with their JSON data being ignored. And fields with an inner class as type will be ignored
|
||||||
|
* during serialization and deserialization.
|
||||||
*
|
*
|
||||||
* <p>By default Gson serializes and deserializes inner classes, but ignores references to the
|
* <p>By default Gson serializes and deserializes inner classes, but ignores references to the
|
||||||
* enclosing instance. Deserialization might not be possible at all when {@link #disableJdkUnsafe()}
|
* enclosing instance. Deserialization might not be possible at all when {@link
|
||||||
* is used (and no custom {@link InstanceCreator} is registered), or it can lead to unexpected
|
* #disableJdkUnsafe()} is used (and no custom {@link InstanceCreator} is registered), or it can
|
||||||
* {@code NullPointerException}s when the deserialized instance is used afterwards.
|
* lead to unexpected {@code NullPointerException}s when the deserialized instance is used
|
||||||
|
* afterwards.
|
||||||
*
|
*
|
||||||
* <p>In general using inner classes with Gson should be avoided; they should be converted to {@code static}
|
* <p>In general using inner classes with Gson should be avoided; they should be converted to
|
||||||
* nested classes if possible.
|
* {@code static} nested classes if possible.
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
|
@ -372,12 +379,12 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to apply a specific naming strategy to an object's fields during
|
* Configures Gson to apply a specific naming strategy to an object's fields during serialization
|
||||||
* serialization and deserialization.
|
* and deserialization.
|
||||||
*
|
*
|
||||||
* <p>The created Gson instance might only use the field naming strategy once for a
|
* <p>The created Gson instance might only use the field naming strategy once for a field and
|
||||||
* field and cache the result. It is not guaranteed that the strategy will be used
|
* cache the result. It is not guaranteed that the strategy will be used again every time the
|
||||||
* again every time the value of a field is serialized or deserialized.
|
* value of a field is serialized or deserialized.
|
||||||
*
|
*
|
||||||
* @param fieldNamingStrategy the naming strategy to apply to the fields
|
* @param fieldNamingStrategy the naming strategy to apply to the fields
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -416,25 +423,24 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to apply a set of exclusion strategies during both serialization and
|
* Configures Gson to apply a set of exclusion strategies during both serialization and
|
||||||
* deserialization. Each of the {@code strategies} will be applied as a disjunction rule.
|
* deserialization. Each of the {@code strategies} will be applied as a disjunction rule. This
|
||||||
* This means that if one of the {@code strategies} suggests that a field (or class) should be
|
* means that if one of the {@code strategies} suggests that a field (or class) should be skipped
|
||||||
* skipped then that field (or object) is skipped during serialization/deserialization.
|
* then that field (or object) is skipped during serialization/deserialization. The strategies are
|
||||||
* The strategies are added to the existing strategies (if any); the existing strategies
|
* added to the existing strategies (if any); the existing strategies are not replaced.
|
||||||
* are not replaced.
|
|
||||||
*
|
*
|
||||||
* <p>Fields are excluded for serialization and deserialization when
|
* <p>Fields are excluded for serialization and deserialization when {@link
|
||||||
* {@link ExclusionStrategy#shouldSkipField(FieldAttributes) shouldSkipField} returns {@code true},
|
* ExclusionStrategy#shouldSkipField(FieldAttributes) shouldSkipField} returns {@code true}, or
|
||||||
* or when {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass} returns {@code true}
|
* when {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass} returns {@code true} for
|
||||||
* for the field type. Gson behaves as if the field did not exist; its value is not serialized
|
* the field type. Gson behaves as if the field did not exist; its value is not serialized and on
|
||||||
* and on deserialization if a JSON member with this name exists it is skipped by default.<br>
|
* deserialization if a JSON member with this name exists it is skipped by default.<br>
|
||||||
* When objects of an excluded type (as determined by
|
* When objects of an excluded type (as determined by {@link
|
||||||
* {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass}) are serialized a
|
* ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass}) are serialized a JSON null is
|
||||||
* JSON null is written to output, and when deserialized the JSON value is skipped and
|
* written to output, and when deserialized the JSON value is skipped and {@code null} is
|
||||||
* {@code null} is returned.
|
* returned.
|
||||||
*
|
*
|
||||||
* <p>The created Gson instance might only use an exclusion strategy once for a field or
|
* <p>The created Gson instance might only use an exclusion strategy once for a field or class and
|
||||||
* class and cache the result. It is not guaranteed that the strategy will be used again
|
* cache the result. It is not guaranteed that the strategy will be used again every time the
|
||||||
* every time the value of a field or a class is serialized or deserialized.
|
* value of a field or a class is serialized or deserialized.
|
||||||
*
|
*
|
||||||
* @param strategies the set of strategy object to apply during object (de)serialization.
|
* @param strategies the set of strategy object to apply during object (de)serialization.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -449,15 +455,14 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to apply the passed in exclusion strategy during serialization.
|
* Configures Gson to apply the passed in exclusion strategy during serialization. If this method
|
||||||
* If this method is invoked numerous times with different exclusion strategy objects
|
* is invoked numerous times with different exclusion strategy objects then the exclusion
|
||||||
* then the exclusion strategies that were added will be applied as a disjunction rule.
|
* strategies that were added will be applied as a disjunction rule. This means that if one of the
|
||||||
* This means that if one of the added exclusion strategies suggests that a field (or
|
* added exclusion strategies suggests that a field (or class) should be skipped then that field
|
||||||
* class) should be skipped then that field (or object) is skipped during its
|
* (or object) is skipped during its serialization.
|
||||||
* serialization.
|
|
||||||
*
|
*
|
||||||
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)}
|
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)} for a
|
||||||
* for a detailed description of the effect of exclusion strategies.
|
* detailed description of the effect of exclusion strategies.
|
||||||
*
|
*
|
||||||
* @param strategy an exclusion strategy to apply during serialization.
|
* @param strategy an exclusion strategy to apply during serialization.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -470,15 +475,14 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to apply the passed in exclusion strategy during deserialization.
|
* Configures Gson to apply the passed in exclusion strategy during deserialization. If this
|
||||||
* If this method is invoked numerous times with different exclusion strategy objects
|
* method is invoked numerous times with different exclusion strategy objects then the exclusion
|
||||||
* then the exclusion strategies that were added will be applied as a disjunction rule.
|
* strategies that were added will be applied as a disjunction rule. This means that if one of the
|
||||||
* This means that if one of the added exclusion strategies suggests that a field (or
|
* added exclusion strategies suggests that a field (or class) should be skipped then that field
|
||||||
* class) should be skipped then that field (or object) is skipped during its
|
* (or object) is skipped during its deserialization.
|
||||||
* deserialization.
|
|
||||||
*
|
*
|
||||||
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)}
|
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)} for a
|
||||||
* for a detailed description of the effect of exclusion strategies.
|
* detailed description of the effect of exclusion strategies.
|
||||||
*
|
*
|
||||||
* @param strategy an exclusion strategy to apply during deserialization.
|
* @param strategy an exclusion strategy to apply during deserialization.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -504,8 +508,9 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to output JSON that uses a certain kind of formatting style (for example newline and indent).
|
* Configures Gson to output JSON that uses a certain kind of formatting style (for example
|
||||||
* This option only affects JSON serialization. By default Gson produces compact JSON output without any formatting.
|
* 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.
|
* @param formattingStyle the formatting style to use.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -519,9 +524,8 @@ public final class GsonBuilder {
|
||||||
/**
|
/**
|
||||||
* Sets the strictness of this builder to {@link Strictness#LENIENT}.
|
* Sets the strictness of this builder to {@link Strictness#LENIENT}.
|
||||||
*
|
*
|
||||||
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with
|
* @deprecated This method is equivalent to calling {@link #setStrictness(Strictness)} with {@link
|
||||||
* {@link Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
|
* Strictness#LENIENT}: {@code setStrictness(Strictness.LENIENT)}
|
||||||
*
|
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
|
||||||
* @see JsonReader#setStrictness(Strictness)
|
* @see JsonReader#setStrictness(Strictness)
|
||||||
* @see JsonWriter#setStrictness(Strictness)
|
* @see JsonWriter#setStrictness(Strictness)
|
||||||
|
@ -535,10 +539,9 @@ public final class GsonBuilder {
|
||||||
/**
|
/**
|
||||||
* Sets the strictness of this builder to the provided parameter.
|
* Sets the strictness of this builder to the provided parameter.
|
||||||
*
|
*
|
||||||
* <p>This changes how strict the
|
* <p>This changes how strict the <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON
|
||||||
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a> is enforced when parsing or
|
* specification</a> is enforced when parsing or writing JSON. For details on this, refer to
|
||||||
* writing JSON. For details on this, refer to {@link JsonReader#setStrictness(Strictness)} and
|
* {@link JsonReader#setStrictness(Strictness)} and {@link JsonWriter#setStrictness(Strictness)}.
|
||||||
* {@link JsonWriter#setStrictness(Strictness)}.</p>
|
|
||||||
*
|
*
|
||||||
* @param strictness the new strictness mode. May not be {@code null}.
|
* @param strictness the new strictness mode. May not be {@code null}.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern.
|
||||||
|
@ -574,68 +577,94 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to serialize {@code Date} objects according to the pattern provided. You can
|
* Configures Gson to serialize {@code Date} objects according to the pattern provided. You can
|
||||||
* call this method or {@link #setDateFormat(int)} multiple times, but only the last invocation
|
* call this method or {@link #setDateFormat(int, int)} multiple times, but only the last
|
||||||
* will be used to decide the serialization format.
|
* invocation will be used to decide the serialization format.
|
||||||
*
|
*
|
||||||
* <p>The date format will be used to serialize and deserialize {@link java.util.Date} and in case
|
* <p>The date format will be used to serialize and deserialize {@link java.util.Date} and in case
|
||||||
* the {@code java.sql} module is present, also {@link java.sql.Timestamp} and {@link java.sql.Date}.
|
* the {@code java.sql} module is present, also {@link java.sql.Timestamp} and {@link
|
||||||
|
* java.sql.Date}.
|
||||||
*
|
*
|
||||||
* <p>Note that this pattern must abide by the convention provided by {@code SimpleDateFormat}
|
* <p>Note that this pattern must abide by the convention provided by {@code SimpleDateFormat}
|
||||||
* class. See the documentation in {@link java.text.SimpleDateFormat} for more information on
|
* class. See the documentation in {@link SimpleDateFormat} for more information on valid date and
|
||||||
* valid date and time patterns.</p>
|
* time patterns.
|
||||||
*
|
*
|
||||||
* @param pattern the pattern that dates will be serialized/deserialized to/from
|
* @param pattern the pattern that dates will be serialized/deserialized to/from; can be {@code
|
||||||
|
* null} to reset the pattern
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
* @throws IllegalArgumentException if the pattern is invalid
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public GsonBuilder setDateFormat(String pattern) {
|
public GsonBuilder setDateFormat(String pattern) {
|
||||||
// TODO(Joel): Make this fail fast if it is an invalid date format
|
if (pattern != null) {
|
||||||
|
try {
|
||||||
|
new SimpleDateFormat(pattern);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Throw exception if it is an invalid date format
|
||||||
|
throw new IllegalArgumentException("The date pattern '" + pattern + "' is not valid", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.datePattern = pattern;
|
this.datePattern = pattern;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson to serialize {@code Date} objects according to the style value provided.
|
* Configures Gson to serialize {@code Date} objects according to the date style value provided.
|
||||||
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
|
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
|
||||||
* invocation will be used to decide the serialization format.
|
* invocation will be used to decide the serialization format. This methods leaves the current
|
||||||
|
* 'time style' unchanged.
|
||||||
*
|
*
|
||||||
* <p>Note that this style value should be one of the predefined constants in the
|
* <p>Note that this style value should be one of the predefined constants in the {@link
|
||||||
* {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
|
* DateFormat} class, such as {@link DateFormat#MEDIUM}. See the documentation of the {@link
|
||||||
* information on the valid style constants.</p>
|
* DateFormat} class for more information on the valid style constants.
|
||||||
*
|
*
|
||||||
* @param style the predefined date style that date objects will be serialized/deserialized
|
* @deprecated Counterintuitively, despite this method taking only a 'date style' Gson will use a
|
||||||
* to/from
|
* format which includes both date and time, with the 'time style' being the last value set by
|
||||||
|
* {@link #setDateFormat(int, int)}. Therefore prefer using {@link #setDateFormat(int, int)}
|
||||||
|
* and explicitly provide the desired 'time style'.
|
||||||
|
* @param dateStyle the predefined date style that date objects will be serialized/deserialized
|
||||||
|
* to/from
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
* @throws IllegalArgumentException if the style is invalid
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public GsonBuilder setDateFormat(int style) {
|
@Deprecated
|
||||||
this.dateStyle = style;
|
public GsonBuilder setDateFormat(int dateStyle) {
|
||||||
|
this.dateStyle = checkDateFormatStyle(dateStyle);
|
||||||
this.datePattern = null;
|
this.datePattern = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson 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
|
||||||
* You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
|
* can call this method or {@link #setDateFormat(String)} multiple times, but only the last
|
||||||
* invocation will be used to decide the serialization format.
|
* invocation will be used to decide the serialization format.
|
||||||
*
|
*
|
||||||
* <p>Note that this style value should be one of the predefined constants in the
|
* <p>Note that this style value should be one of the predefined constants in the {@link
|
||||||
* {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
|
* DateFormat} class, such as {@link DateFormat#MEDIUM}. See the documentation of the {@link
|
||||||
* information on the valid style constants.</p>
|
* DateFormat} class for more information on the valid style constants.
|
||||||
*
|
*
|
||||||
* @param dateStyle the predefined date style that date objects will be serialized/deserialized
|
* @param dateStyle the predefined date style that date objects will be serialized/deserialized
|
||||||
* to/from
|
* to/from
|
||||||
* @param timeStyle the predefined style for the time portion of the date objects
|
* @param timeStyle the predefined style for the time portion of the date objects
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
* @throws IllegalArgumentException if the style values are invalid
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public GsonBuilder setDateFormat(int dateStyle, int timeStyle) {
|
public GsonBuilder setDateFormat(int dateStyle, int timeStyle) {
|
||||||
this.dateStyle = dateStyle;
|
this.dateStyle = checkDateFormatStyle(dateStyle);
|
||||||
this.timeStyle = timeStyle;
|
this.timeStyle = checkDateFormatStyle(timeStyle);
|
||||||
this.datePattern = null;
|
this.datePattern = null;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int checkDateFormatStyle(int style) {
|
||||||
|
// Valid DateFormat styles are: 0, 1, 2, 3 (FULL, LONG, MEDIUM, SHORT)
|
||||||
|
if (style < 0 || style > 3) {
|
||||||
|
throw new IllegalArgumentException("Invalid style: " + style);
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson for custom serialization or deserialization. This method combines the
|
* Configures Gson for custom serialization or deserialization. This method combines the
|
||||||
* registration of an {@link TypeAdapter}, {@link InstanceCreator}, {@link JsonSerializer}, and a
|
* registration of an {@link TypeAdapter}, {@link InstanceCreator}, {@link JsonSerializer}, and a
|
||||||
|
@ -645,25 +674,36 @@ public final class GsonBuilder {
|
||||||
*
|
*
|
||||||
* <p>This registers the type specified and no other types: you must manually register related
|
* <p>This registers the type specified and no other types: you must manually register related
|
||||||
* types! For example, applications registering {@code boolean.class} should also register {@code
|
* types! For example, applications registering {@code boolean.class} should also register {@code
|
||||||
* Boolean.class}.
|
* Boolean.class}. And when registering an adapter for a class which has subclasses, you might
|
||||||
|
* also want to register the adapter for subclasses, or use {@link
|
||||||
|
* #registerTypeHierarchyAdapter(Class, Object)} instead.
|
||||||
*
|
*
|
||||||
* <p>{@link JsonSerializer} and {@link JsonDeserializer} are made "{@code null}-safe". This
|
* <p>{@link JsonSerializer} and {@link JsonDeserializer} are made "{@code null}-safe". This means
|
||||||
* means when trying to serialize {@code null}, Gson will write a JSON {@code null} and the
|
* when trying to serialize {@code null}, Gson will write a JSON {@code null} and the serializer
|
||||||
* serializer is not called. Similarly when deserializing a JSON {@code null}, Gson will emit
|
* is not called. Similarly when deserializing a JSON {@code null}, Gson will emit {@code null}
|
||||||
* {@code null} without calling the deserializer. If it is desired to handle {@code null} values,
|
* without calling the deserializer. If it is desired to handle {@code null} values, a {@link
|
||||||
* a {@link TypeAdapter} should be used instead.
|
* TypeAdapter} should be used instead.
|
||||||
*
|
*
|
||||||
* @param type the type definition for the type adapter being registered
|
* @param type the type definition for the type adapter being registered
|
||||||
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter},
|
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter}, {@link
|
||||||
* {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
|
* InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
* @throws IllegalArgumentException if the type adapter being registered is for {@code Object}
|
||||||
|
* class or {@link JsonElement} or any of its subclasses
|
||||||
|
* @see #registerTypeHierarchyAdapter(Class, Object)
|
||||||
*/
|
*/
|
||||||
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
|
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
|
||||||
Objects.requireNonNull(type);
|
Objects.requireNonNull(type);
|
||||||
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|
$Gson$Preconditions.checkArgument(
|
||||||
|| typeAdapter instanceof JsonDeserializer<?>
|
typeAdapter instanceof JsonSerializer<?>
|
||||||
|| typeAdapter instanceof InstanceCreator<?>
|
|| typeAdapter instanceof JsonDeserializer<?>
|
||||||
|| typeAdapter instanceof TypeAdapter<?>);
|
|| typeAdapter instanceof InstanceCreator<?>
|
||||||
|
|| typeAdapter instanceof TypeAdapter<?>);
|
||||||
|
|
||||||
|
if (isTypeObjectOrJsonElement(type)) {
|
||||||
|
throw new IllegalArgumentException("Cannot override built-in adapter for " + type);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeAdapter instanceof InstanceCreator<?>) {
|
if (typeAdapter instanceof InstanceCreator<?>) {
|
||||||
instanceCreators.put(type, (InstanceCreator<?>) typeAdapter);
|
instanceCreators.put(type, (InstanceCreator<?>) typeAdapter);
|
||||||
}
|
}
|
||||||
|
@ -673,21 +713,27 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
if (typeAdapter instanceof TypeAdapter<?>) {
|
if (typeAdapter instanceof TypeAdapter<?>) {
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
TypeAdapterFactory factory = TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter);
|
TypeAdapterFactory factory =
|
||||||
|
TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter) typeAdapter);
|
||||||
factories.add(factory);
|
factories.add(factory);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isTypeObjectOrJsonElement(Type type) {
|
||||||
|
return type instanceof Class
|
||||||
|
&& (type == Object.class || JsonElement.class.isAssignableFrom((Class<?>) type));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a factory for type adapters. Registering a factory is useful when the type
|
* Registers a factory for type adapters. Registering a factory is useful when the type adapter
|
||||||
* adapter needs to be configured based on the type of the field being processed. Gson
|
* needs to be configured based on the type of the field being processed. Gson is designed to
|
||||||
* is designed to handle a large number of factories, so you should consider registering
|
* handle a large number of factories, so you should consider registering them to be at par with
|
||||||
* them to be at par with registering an individual type adapter.
|
* registering an individual type adapter.
|
||||||
*
|
*
|
||||||
* <p>The created Gson instance might only use the factory once to create an adapter for
|
* <p>The created Gson instance might only use the factory once to create an adapter for a
|
||||||
* a specific type and cache the result. It is not guaranteed that the factory will be used
|
* specific type and cache the result. It is not guaranteed that the factory will be used again
|
||||||
* again every time the type is serialized or deserialized.
|
* every time the type is serialized or deserialized.
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
*/
|
*/
|
||||||
|
@ -699,29 +745,38 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
|
* Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
|
||||||
* This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and
|
* This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and a
|
||||||
* a {@link JsonDeserializer}. If a type adapter was previously registered for the specified
|
* {@link JsonDeserializer}. If a type adapter was previously registered for the specified type
|
||||||
* type hierarchy, it is overridden. If a type adapter is registered for a specific type in
|
* hierarchy, it is overridden. If a type adapter is registered for a specific type in the type
|
||||||
* the type hierarchy, it will be invoked instead of the one registered for the type hierarchy.
|
* hierarchy, it will be invoked instead of the one registered for the type hierarchy.
|
||||||
*
|
*
|
||||||
* @param baseType the class definition for the type adapter being registered for the base class
|
* @param baseType the class definition for the type adapter being registered for the base class
|
||||||
* or interface
|
* or interface
|
||||||
* @param typeAdapter This object must implement at least one of {@link TypeAdapter},
|
* @param typeAdapter This object must implement at least one of {@link TypeAdapter}, {@link
|
||||||
* {@link JsonSerializer} or {@link JsonDeserializer} interfaces.
|
* JsonSerializer} or {@link JsonDeserializer} interfaces.
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
* @throws IllegalArgumentException if the type adapter being registered is for {@link
|
||||||
|
* JsonElement} or any of its subclasses
|
||||||
* @since 1.7
|
* @since 1.7
|
||||||
*/
|
*/
|
||||||
public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
|
public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
|
||||||
Objects.requireNonNull(baseType);
|
Objects.requireNonNull(baseType);
|
||||||
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|
$Gson$Preconditions.checkArgument(
|
||||||
|| typeAdapter instanceof JsonDeserializer<?>
|
typeAdapter instanceof JsonSerializer<?>
|
||||||
|| typeAdapter instanceof TypeAdapter<?>);
|
|| typeAdapter instanceof JsonDeserializer<?>
|
||||||
|
|| typeAdapter instanceof TypeAdapter<?>);
|
||||||
|
|
||||||
|
if (JsonElement.class.isAssignableFrom(baseType)) {
|
||||||
|
throw new IllegalArgumentException("Cannot override built-in adapter for " + baseType);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) {
|
if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) {
|
||||||
hierarchyFactories.add(TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
|
hierarchyFactories.add(TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
|
||||||
}
|
}
|
||||||
if (typeAdapter instanceof TypeAdapter<?>) {
|
if (typeAdapter instanceof TypeAdapter<?>) {
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
TypeAdapterFactory factory = TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter);
|
TypeAdapterFactory factory =
|
||||||
|
TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter) typeAdapter);
|
||||||
factories.add(factory);
|
factories.add(factory);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -729,20 +784,19 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Section 6 of <a href="https://www.ietf.org/rfc/rfc8259.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,
|
* special double values (NaN, Infinity, -Infinity). However, <a
|
||||||
* <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
|
* 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
|
* specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
|
||||||
* values. Moreover, most JavaScript engines will accept these special values in JSON without
|
* values. Moreover, most JavaScript engines will accept these special values in JSON without
|
||||||
* problem. So, at a practical level, it makes sense to accept these values as valid JSON even
|
* problem. So, at a practical level, it makes sense to accept these values as valid JSON even
|
||||||
* though JSON specification disallows them.
|
* though JSON specification disallows them.
|
||||||
*
|
*
|
||||||
* <p>Gson always accepts these special values during deserialization. However, it outputs
|
* <p>Gson always accepts these special values during deserialization. However, it outputs
|
||||||
* strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN},
|
* strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN}, {@link
|
||||||
* {@link Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value
|
* Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value {@link
|
||||||
* {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it
|
* Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it will throw
|
||||||
* will throw an {@link IllegalArgumentException}. This method provides a way to override the
|
* an {@link IllegalArgumentException}. This method provides a way to override the default
|
||||||
* default behavior when you know that the JSON receiver will be able to handle these special
|
* behavior when you know that the JSON receiver will be able to handle these special values.
|
||||||
* values.
|
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
|
@ -755,15 +809,14 @@ public final class GsonBuilder {
|
||||||
/**
|
/**
|
||||||
* Disables usage of JDK's {@code sun.misc.Unsafe}.
|
* Disables usage of JDK's {@code sun.misc.Unsafe}.
|
||||||
*
|
*
|
||||||
* <p>By default Gson uses {@code Unsafe} to create instances of classes which don't have
|
* <p>By default Gson uses {@code Unsafe} to create instances of classes which don't have a
|
||||||
* a no-args constructor. However, {@code Unsafe} might not be available for all Java
|
* no-args constructor. However, {@code Unsafe} might not be available for all Java runtimes. For
|
||||||
* runtimes. For example Android does not provide {@code Unsafe}, or only with limited
|
* example Android does not provide {@code Unsafe}, or only with limited functionality.
|
||||||
* functionality. Additionally {@code Unsafe} creates instances without executing any
|
* Additionally {@code Unsafe} creates instances without executing any constructor or initializer
|
||||||
* constructor or initializer block, or performing initialization of field values. This can
|
* block, or performing initialization of field values. This can lead to surprising and difficult
|
||||||
* lead to surprising and difficult to debug errors.
|
* to debug errors. Therefore, to get reliable behavior regardless of which runtime is used, and
|
||||||
* Therefore, to get reliable behavior regardless of which runtime is used, and to detect
|
* to detect classes which cannot be deserialized in an early stage of development, this method
|
||||||
* classes which cannot be deserialized in an early stage of development, this method allows
|
* allows disabling usage of {@code Unsafe}.
|
||||||
* disabling usage of {@code Unsafe}.
|
|
||||||
*
|
*
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
* @since 2.9.0
|
* @since 2.9.0
|
||||||
|
@ -774,20 +827,20 @@ public final class GsonBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a reflection access filter. A reflection access filter prevents Gson from using
|
* Adds a reflection access filter. A reflection access filter prevents Gson from using reflection
|
||||||
* reflection for the serialization and deserialization of certain classes. The logic in
|
* for the serialization and deserialization of certain classes. The logic in the filter specifies
|
||||||
* the filter specifies which classes those are.
|
* which classes those are.
|
||||||
*
|
*
|
||||||
* <p>Filters will be invoked in reverse registration order, that is, the most recently
|
* <p>Filters will be invoked in reverse registration order, that is, the most recently added
|
||||||
* added filter will be invoked first.
|
* filter will be invoked first.
|
||||||
*
|
*
|
||||||
* <p>By default Gson has no filters configured and will try to use reflection for
|
* <p>By default Gson has no filters configured and will try to use reflection for all classes for
|
||||||
* all classes for which no {@link TypeAdapter} has been registered, and for which no
|
* which no {@link TypeAdapter} has been registered, and for which no built-in Gson {@code
|
||||||
* built-in Gson {@code TypeAdapter} exists.
|
* TypeAdapter} exists.
|
||||||
*
|
*
|
||||||
* <p>The created Gson instance might only use an access filter once for a class or its
|
* <p>The created Gson instance might only use an access filter once for a class or its members
|
||||||
* members and cache the result. It is not guaranteed that the filter will be used again
|
* and cache the result. It is not guaranteed that the filter will be used again every time a
|
||||||
* every time a class or its members are accessed during serialization or deserialization.
|
* class or its members are accessed during serialization or deserialization.
|
||||||
*
|
*
|
||||||
* @param filter filter to add
|
* @param filter filter to add
|
||||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||||
|
@ -806,7 +859,8 @@ public final class GsonBuilder {
|
||||||
* @return an instance of Gson configured with the options currently set in this builder
|
* @return an instance of Gson configured with the options currently set in this builder
|
||||||
*/
|
*/
|
||||||
public Gson create() {
|
public Gson create() {
|
||||||
List<TypeAdapterFactory> factories = new ArrayList<>(this.factories.size() + this.hierarchyFactories.size() + 3);
|
List<TypeAdapterFactory> factories =
|
||||||
|
new ArrayList<>(this.factories.size() + this.hierarchyFactories.size() + 3);
|
||||||
factories.addAll(this.factories);
|
factories.addAll(this.factories);
|
||||||
Collections.reverse(factories);
|
Collections.reverse(factories);
|
||||||
|
|
||||||
|
@ -816,17 +870,33 @@ public final class GsonBuilder {
|
||||||
|
|
||||||
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
|
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
|
||||||
|
|
||||||
return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
|
return new Gson(
|
||||||
serializeNulls, complexMapKeySerialization, duplicateMapKeyDeserialization,
|
excluder,
|
||||||
generateNonExecutableJson, escapeHtmlChars, formattingStyle, strictness, omitQuotes,
|
fieldNamingPolicy,
|
||||||
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
|
new HashMap<>(instanceCreators),
|
||||||
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
|
serializeNulls,
|
||||||
new ArrayList<>(this.hierarchyFactories), factories,
|
complexMapKeySerialization,
|
||||||
objectToNumberStrategy, numberToNumberStrategy, new ArrayList<>(reflectionFilters));
|
duplicateMapKeyDeserialization,
|
||||||
|
generateNonExecutableJson,
|
||||||
|
escapeHtmlChars,
|
||||||
|
formattingStyle,
|
||||||
|
strictness, omitQuotes,
|
||||||
|
serializeSpecialFloatingPointValues,
|
||||||
|
useJdkUnsafe,
|
||||||
|
longSerializationPolicy,
|
||||||
|
datePattern,
|
||||||
|
dateStyle,
|
||||||
|
timeStyle,
|
||||||
|
new ArrayList<>(this.factories),
|
||||||
|
new ArrayList<>(this.hierarchyFactories),
|
||||||
|
factories,
|
||||||
|
objectToNumberStrategy,
|
||||||
|
numberToNumberStrategy,
|
||||||
|
new ArrayList<>(reflectionFilters));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
|
private static void addTypeAdaptersForDate(
|
||||||
List<TypeAdapterFactory> factories) {
|
String datePattern, int dateStyle, int timeStyle, List<TypeAdapterFactory> factories) {
|
||||||
TypeAdapterFactory dateAdapterFactory;
|
TypeAdapterFactory dateAdapterFactory;
|
||||||
boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES;
|
boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES;
|
||||||
TypeAdapterFactory sqlTimestampAdapterFactory = null;
|
TypeAdapterFactory sqlTimestampAdapterFactory = null;
|
||||||
|
@ -836,15 +906,19 @@ public final class GsonBuilder {
|
||||||
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(datePattern);
|
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(datePattern);
|
||||||
|
|
||||||
if (sqlTypesSupported) {
|
if (sqlTypesSupported) {
|
||||||
sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(datePattern);
|
sqlTimestampAdapterFactory =
|
||||||
|
SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(datePattern);
|
||||||
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(datePattern);
|
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(datePattern);
|
||||||
}
|
}
|
||||||
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
|
} else if (dateStyle != DateFormat.DEFAULT || timeStyle != DateFormat.DEFAULT) {
|
||||||
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(dateStyle, timeStyle);
|
dateAdapterFactory =
|
||||||
|
DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(dateStyle, timeStyle);
|
||||||
|
|
||||||
if (sqlTypesSupported) {
|
if (sqlTypesSupported) {
|
||||||
sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
|
sqlTimestampAdapterFactory =
|
||||||
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
|
SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
|
||||||
|
sqlDateAdapterFactory =
|
||||||
|
SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,14 +20,15 @@ import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is implemented to create instances of a class that does not define a no-args
|
* This interface is implemented to create instances of a class that does not define a no-args
|
||||||
* constructor. If you can modify the class, you should instead add a private, or public
|
* constructor. If you can modify the class, you should instead add a private, or public no-args
|
||||||
* no-args constructor. However, that is not possible for library classes, such as JDK classes, or
|
* constructor. However, that is not possible for library classes, such as JDK classes, or a
|
||||||
* a third-party library that you do not have source-code of. In such cases, you should define an
|
* third-party library that you do not have source-code of. In such cases, you should define an
|
||||||
* instance creator for the class. Implementations of this interface should be registered with
|
* instance creator for the class. Implementations of this interface should be registered with
|
||||||
* {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use
|
* {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use
|
||||||
* them.
|
* them.
|
||||||
* <p>Let us look at an example where defining an InstanceCreator might be useful. The
|
*
|
||||||
* {@code Id} class defined below does not have a default no-args constructor.</p>
|
* <p>Let us look at an example where defining an InstanceCreator might be useful. The {@code Id}
|
||||||
|
* class defined below does not have a default no-args constructor.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class Id<T> {
|
* public class Id<T> {
|
||||||
|
@ -42,7 +43,7 @@ import java.lang.reflect.Type;
|
||||||
*
|
*
|
||||||
* <p>If Gson encounters an object of type {@code Id} during deserialization, it will throw an
|
* <p>If Gson encounters an object of type {@code Id} during deserialization, it will throw an
|
||||||
* exception. The easiest way to solve this problem will be to add a (public or private) no-args
|
* exception. The easiest way to solve this problem will be to add a (public or private) no-args
|
||||||
* constructor as follows:</p>
|
* constructor as follows:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* private Id() {
|
* private Id() {
|
||||||
|
@ -51,8 +52,8 @@ import java.lang.reflect.Type;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>However, let us assume that the developer does not have access to the source-code of the
|
* <p>However, let us assume that the developer does not have access to the source-code of the
|
||||||
* {@code Id} class, or does not want to define a no-args constructor for it. The developer
|
* {@code Id} class, or does not want to define a no-args constructor for it. The developer can
|
||||||
* can solve this problem by defining an {@code InstanceCreator} for {@code Id}:</p>
|
* solve this problem by defining an {@code InstanceCreator} for {@code Id}:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* class IdInstanceCreator implements InstanceCreator<Id> {
|
* class IdInstanceCreator implements InstanceCreator<Id> {
|
||||||
|
@ -64,17 +65,15 @@ import java.lang.reflect.Type;
|
||||||
*
|
*
|
||||||
* <p>Note that it does not matter what the fields of the created instance contain since Gson will
|
* <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.
|
* <i>new</i> object is returned, not a common object since its fields will be overwritten. The
|
||||||
* The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p>
|
* developer will need to register {@code IdInstanceCreator} with Gson as follows:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
|
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param <T> the type of object that will be created by this implementation.
|
* @param <T> the type of object that will be created by this implementation.
|
||||||
*
|
|
||||||
* @see GsonBuilder#registerTypeAdapter(Type, Object)
|
* @see GsonBuilder#registerTypeAdapter(Type, Object)
|
||||||
*
|
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
|
@ -82,10 +81,10 @@ public interface InstanceCreator<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gson invokes this call-back method during deserialization to create an instance of the
|
* 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
|
* specified type. The fields of the returned instance are overwritten with the data present in
|
||||||
* in the JSON. Since the prior contents of the object are destroyed and overwritten, do not
|
* the JSON. Since the prior contents of the object are destroyed and overwritten, do not return
|
||||||
* return an instance that is useful elsewhere. In particular, do not return a common instance,
|
* an instance that is useful elsewhere. In particular, do not return a common instance, always
|
||||||
* always use {@code new} to create a new instance.
|
* use {@code new} to create a new instance.
|
||||||
*
|
*
|
||||||
* @param type the parameterized T represented as a {@link Type}.
|
* @param type the parameterized T represented as a {@link Type}.
|
||||||
* @return a default object instance of type T.
|
* @return a default object instance of type T.
|
||||||
|
|
|
@ -38,9 +38,7 @@ import java.util.List;
|
||||||
public final class JsonArray extends JsonElement implements Iterable<JsonElement> {
|
public final class JsonArray extends JsonElement implements Iterable<JsonElement> {
|
||||||
private final ArrayList<JsonElement> elements;
|
private final ArrayList<JsonElement> elements;
|
||||||
|
|
||||||
/**
|
/** Creates an empty JsonArray. */
|
||||||
* Creates an empty JsonArray.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation") // superclass constructor
|
@SuppressWarnings("deprecation") // superclass constructor
|
||||||
public JsonArray() {
|
public JsonArray() {
|
||||||
elements = new ArrayList<>();
|
elements = new ArrayList<>();
|
||||||
|
@ -50,8 +48,7 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
* Creates an empty JsonArray with the desired initial capacity.
|
* Creates an empty JsonArray with the desired initial capacity.
|
||||||
*
|
*
|
||||||
* @param capacity initial capacity.
|
* @param capacity initial capacity.
|
||||||
* @throws IllegalArgumentException if the {@code capacity} is
|
* @throws IllegalArgumentException if the {@code capacity} is negative
|
||||||
* negative
|
|
||||||
* @since 2.8.1
|
* @since 2.8.1
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation") // superclass constructor
|
@SuppressWarnings("deprecation") // superclass constructor
|
||||||
|
@ -150,8 +147,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the first occurrence of the specified element from this array, if it is present.
|
* Removes the first occurrence of the specified element from this array, if it is present. If the
|
||||||
* If the array does not contain the element, it is unchanged.
|
* array does not contain the element, it is unchanged.
|
||||||
*
|
*
|
||||||
* @param element element to be removed from this array, if present
|
* @param element element to be removed from this array, if present
|
||||||
* @return true if this array contained the specified element, false otherwise
|
* @return true if this array contained the specified element, false otherwise
|
||||||
|
@ -162,9 +159,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the element at the specified position in this array. Shifts any subsequent elements
|
* Removes the element at the specified position in this array. Shifts any subsequent elements to
|
||||||
* to the left (subtracts one from their indices). Returns the element that was removed from
|
* the left (subtracts one from their indices). Returns the element that was removed from the
|
||||||
* the array.
|
* array.
|
||||||
*
|
*
|
||||||
* @param index index the index of the element to be removed
|
* @param index index the index of the element to be removed
|
||||||
* @return the element previously at the specified position
|
* @return the element previously at the specified position
|
||||||
|
@ -221,8 +218,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
*
|
*
|
||||||
* @param i the index of the element that is being sought.
|
* @param i the index of the element that is being sought.
|
||||||
* @return the element present at the i-th index.
|
* @return the element present at the i-th index.
|
||||||
* @throws IndexOutOfBoundsException if i is negative or greater than or equal to the
|
* @throws IndexOutOfBoundsException if {@code i} is negative or greater than or equal to the
|
||||||
* {@link #size()} of the array.
|
* {@link #size()} of the array.
|
||||||
*/
|
*/
|
||||||
public JsonElement get(int i) {
|
public JsonElement get(int i) {
|
||||||
return elements.get(i);
|
return elements.get(i);
|
||||||
|
@ -237,9 +234,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a {@link Number} if it contains a single element.
|
* Convenience method to get this array as a {@link Number} if it contains a single element. This
|
||||||
* This method calls {@link JsonElement#getAsNumber()} on the element, therefore any
|
* method calls {@link JsonElement#getAsNumber()} on the element, therefore any of the exceptions
|
||||||
* of the exceptions declared by that method can occur.
|
* declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a number if it is single element array.
|
* @return this element as a number if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -250,9 +247,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a {@link String} if it contains a single element.
|
* Convenience method to get this array as a {@link String} if it contains a single element. This
|
||||||
* This method calls {@link JsonElement#getAsString()} on the element, therefore any
|
* method calls {@link JsonElement#getAsString()} on the element, therefore any of the exceptions
|
||||||
* of the exceptions declared by that method can occur.
|
* declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a String if it is single element array.
|
* @return this element as a String if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -263,9 +260,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a double if it contains a single element.
|
* Convenience method to get this array as a double if it contains a single element. This method
|
||||||
* This method calls {@link JsonElement#getAsDouble()} on the element, therefore any
|
* calls {@link JsonElement#getAsDouble()} on the element, therefore any of the exceptions
|
||||||
* of the exceptions declared by that method can occur.
|
* declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a double if it is single element array.
|
* @return this element as a double if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -277,8 +274,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a {@link BigDecimal} if it contains a single element.
|
* Convenience method to get this array as a {@link BigDecimal} if it contains a single element.
|
||||||
* This method calls {@link JsonElement#getAsBigDecimal()} on the element, therefore any
|
* This method calls {@link JsonElement#getAsBigDecimal()} on the element, therefore any of the
|
||||||
* of the exceptions declared by that method can occur.
|
* exceptions declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a {@link BigDecimal} if it is single element array.
|
* @return this element as a {@link BigDecimal} if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -291,8 +288,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a {@link BigInteger} if it contains a single element.
|
* Convenience method to get this array as a {@link BigInteger} if it contains a single element.
|
||||||
* This method calls {@link JsonElement#getAsBigInteger()} on the element, therefore any
|
* This method calls {@link JsonElement#getAsBigInteger()} on the element, therefore any of the
|
||||||
* of the exceptions declared by that method can occur.
|
* exceptions declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a {@link BigInteger} if it is single element array.
|
* @return this element as a {@link BigInteger} if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -304,9 +301,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a float if it contains a single element.
|
* Convenience method to get this array as a float if it contains a single element. This method
|
||||||
* This method calls {@link JsonElement#getAsFloat()} on the element, therefore any
|
* calls {@link JsonElement#getAsFloat()} on the element, therefore any of the exceptions declared
|
||||||
* of the exceptions declared by that method can occur.
|
* by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a float if it is single element array.
|
* @return this element as a float if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -317,9 +314,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a long if it contains a single element.
|
* Convenience method to get this array as a long if it contains a single element. This method
|
||||||
* This method calls {@link JsonElement#getAsLong()} on the element, therefore any
|
* calls {@link JsonElement#getAsLong()} on the element, therefore any of the exceptions declared
|
||||||
* of the exceptions declared by that method can occur.
|
* by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a long if it is single element array.
|
* @return this element as a long if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -330,9 +327,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as an integer if it contains a single element.
|
* Convenience method to get this array as an integer if it contains a single element. This method
|
||||||
* This method calls {@link JsonElement#getAsInt()} on the element, therefore any
|
* calls {@link JsonElement#getAsInt()} on the element, therefore any of the exceptions declared
|
||||||
* of the exceptions declared by that method can occur.
|
* by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as an integer if it is single element array.
|
* @return this element as an integer if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -343,9 +340,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a primitive byte if it contains a single element.
|
* Convenience method to get this array as a primitive byte if it contains a single element. This
|
||||||
* This method calls {@link JsonElement#getAsByte()} on the element, therefore any
|
* method calls {@link JsonElement#getAsByte()} on the element, therefore any of the exceptions
|
||||||
* of the exceptions declared by that method can occur.
|
* declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive byte if it is single element array.
|
* @return this element as a primitive byte if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -356,14 +353,14 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a character if it contains a single element.
|
* Convenience method to get this array as a character if it contains a single element. This
|
||||||
* This method calls {@link JsonElement#getAsCharacter()} on the element, therefore any
|
* method calls {@link JsonElement#getAsCharacter()} on the element, therefore any of the
|
||||||
* of the exceptions declared by that method can occur.
|
* exceptions declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive short if it is single element array.
|
* @return this element as a primitive short if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
* @deprecated This method is misleading, as it does not get this element as a char but rather as
|
* @deprecated This method is misleading, as it does not get this element as a char but rather as
|
||||||
* a string's first character.
|
* a string's first character.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
|
@ -372,9 +369,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a primitive short if it contains a single element.
|
* Convenience method to get this array as a primitive short if it contains a single element. This
|
||||||
* This method calls {@link JsonElement#getAsShort()} on the element, therefore any
|
* method calls {@link JsonElement#getAsShort()} on the element, therefore any of the exceptions
|
||||||
* of the exceptions declared by that method can occur.
|
* declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive short if it is single element array.
|
* @return this element as a primitive short if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -385,9 +382,9 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this array as a boolean if it contains a single element.
|
* Convenience method to get this array as a boolean if it contains a single element. This method
|
||||||
* This method calls {@link JsonElement#getAsBoolean()} on the element, therefore any
|
* calls {@link JsonElement#getAsBoolean()} on the element, therefore any of the exceptions
|
||||||
* of the exceptions declared by that method can occur.
|
* declared by that method can occur.
|
||||||
*
|
*
|
||||||
* @return this element as a boolean if it is single element array.
|
* @return this element as a boolean if it is single element array.
|
||||||
* @throws IllegalStateException if the array is empty or has more than one element.
|
* @throws IllegalStateException if the array is empty or has more than one element.
|
||||||
|
@ -398,12 +395,12 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a mutable {@link List} view of this {@code JsonArray}. Changes to the {@code List}
|
* Returns a mutable {@link List} view of this {@code JsonArray}. Changes to the {@code List} are
|
||||||
* are visible in this {@code JsonArray} and the other way around.
|
* visible in this {@code JsonArray} and the other way around.
|
||||||
*
|
*
|
||||||
* <p>The {@code List} does not permit {@code null} elements. Unlike {@code JsonArray}'s
|
* <p>The {@code List} does not permit {@code null} elements. Unlike {@code JsonArray}'s {@code
|
||||||
* {@code null} handling, a {@link NullPointerException} is thrown when trying to add {@code null}.
|
* null} handling, a {@link NullPointerException} is thrown when trying to add {@code null}. Use
|
||||||
* Use {@link JsonNull} for JSON null values.
|
* {@link JsonNull} for JSON null values.
|
||||||
*
|
*
|
||||||
* @return mutable {@code List} view
|
* @return mutable {@code List} view
|
||||||
* @since 2.10
|
* @since 2.10
|
||||||
|
@ -413,9 +410,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the other object is equal to this. This method only considers
|
* Returns whether the other object is equal to this. This method only considers the other object
|
||||||
* the other object to be equal if it is an instance of {@code JsonArray} and has
|
* to be equal if it is an instance of {@code JsonArray} and has equal elements in the same order.
|
||||||
* equal elements in the same order.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
@ -423,8 +419,8 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash code of this array. This method calculates the hash code based
|
* Returns the hash code of this array. This method calculates the hash code based on the elements
|
||||||
* on the elements of this array.
|
* of this array.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|
|
@ -20,8 +20,7 @@ import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context for deserialization that is passed to a custom deserializer during invocation of its
|
* Context for deserialization that is passed to a custom deserializer during invocation of its
|
||||||
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)}
|
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method.
|
||||||
* method.
|
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -29,10 +28,10 @@ import java.lang.reflect.Type;
|
||||||
public interface JsonDeserializationContext {
|
public interface JsonDeserializationContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes default deserialization on the specified object. It should never be invoked on
|
* Invokes default deserialization on the specified object. It should never be invoked on the
|
||||||
* the element received as a parameter of the
|
* element received as a parameter of the {@link JsonDeserializer#deserialize(JsonElement, Type,
|
||||||
* {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method. Doing
|
* JsonDeserializationContext)} method. Doing so will result in an infinite loop since Gson will
|
||||||
* so will result in an infinite loop since Gson will in-turn call the custom deserializer again.
|
* in-turn call the custom deserializer again.
|
||||||
*
|
*
|
||||||
* @param json the parse tree.
|
* @param json the parse tree.
|
||||||
* @param typeOfT type of the expected return value.
|
* @param typeOfT type of the expected return value.
|
||||||
|
@ -42,4 +41,4 @@ public interface JsonDeserializationContext {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("TypeParameterUnusedInFormals")
|
@SuppressWarnings("TypeParameterUnusedInFormals")
|
||||||
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
|
public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,12 @@ package com.google.gson;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Interface representing a custom deserializer for JSON. You should write a custom
|
* Interface representing a custom deserializer for JSON. You should write a custom deserializer, if
|
||||||
* deserializer, if you are not happy with the default deserialization done by Gson. You will
|
* you are not happy with the default deserialization done by Gson. You will also need to register
|
||||||
* also need to register this deserializer through
|
* this deserializer through {@link GsonBuilder#registerTypeAdapter(Type, Object)}.
|
||||||
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}.</p>
|
|
||||||
*
|
*
|
||||||
* <p>Let us look at example where defining a deserializer will be useful. The {@code Id} class
|
* <p>Let us look at example where defining a deserializer will be useful. The {@code Id} class
|
||||||
* defined below has two fields: {@code clazz} and {@code value}.</p>
|
* defined below has two fields: {@code clazz} and {@code value}.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class Id<T> {
|
* public class Id<T> {
|
||||||
|
@ -41,11 +40,11 @@ import java.lang.reflect.Type;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the
|
* <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the JSON
|
||||||
* JSON string to be <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you already know
|
* string to be <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you already know the
|
||||||
* the type of the field that the {@code Id} will be deserialized into, and hence just want to
|
* type of the field that the {@code Id} will be deserialized into, and hence just want to
|
||||||
* deserialize it from a JSON string {@code 20}. You can achieve that by writing a custom
|
* deserialize it from a JSON string {@code 20}. You can achieve that by writing a custom
|
||||||
* deserializer:</p>
|
* deserializer:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* class IdDeserializer implements JsonDeserializer<Id> {
|
* class IdDeserializer implements JsonDeserializer<Id> {
|
||||||
|
@ -57,34 +56,34 @@ import java.lang.reflect.Type;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>You will also need to register {@code IdDeserializer} with Gson as follows:</p>
|
* <p>You will also need to register {@code IdDeserializer} with Gson as follows:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
|
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>Deserializers should be stateless and thread-safe, otherwise the thread-safety
|
* <p>Deserializers should be stateless and thread-safe, otherwise the thread-safety guarantees of
|
||||||
* guarantees of {@link Gson} might not apply.
|
* {@link Gson} might not apply.
|
||||||
*
|
*
|
||||||
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API
|
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API is more efficient than
|
||||||
* is more efficient than this interface's tree API.
|
* this interface's tree API.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*
|
|
||||||
* @param <T> type for which the deserializer is being registered. It is possible that a
|
* @param <T> type for which the deserializer is being registered. It is possible that a
|
||||||
* deserializer may be asked to deserialize a specific generic type of the T.
|
* deserializer may be asked to deserialize a specific generic type of the T.
|
||||||
*/
|
*/
|
||||||
public interface JsonDeserializer<T> {
|
public interface JsonDeserializer<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gson invokes this call-back method during deserialization when it encounters a field of the
|
* Gson invokes this call-back method during deserialization when it encounters a field of the
|
||||||
* specified type.
|
* specified type.
|
||||||
* <p>In the implementation of this call-back method, you should consider invoking
|
*
|
||||||
* {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
|
* <p>In the implementation of this call-back method, you should consider invoking {@link
|
||||||
* for any non-trivial field of the returned object. However, you should never invoke it on the
|
* JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects for any
|
||||||
* same type passing {@code json} since that will cause an infinite loop (Gson will call your
|
* non-trivial field of the returned object. However, you should never invoke it on the same type
|
||||||
* call-back method again).
|
* passing {@code json} since that will cause an infinite loop (Gson will call your call-back
|
||||||
|
* method again).
|
||||||
*
|
*
|
||||||
* @param json The Json data being deserialized
|
* @param json The Json data being deserialized
|
||||||
* @param typeOfT The type of the Object to deserialize to
|
* @param typeOfT The type of the Object to deserialize to
|
||||||
|
|
|
@ -24,25 +24,24 @@ import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class representing an element of JSON. It could either be a {@link JsonObject}, a
|
* A class representing an element of JSON. It could either be a {@link JsonObject}, a {@link
|
||||||
* {@link JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
|
* JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
public abstract class JsonElement {
|
public abstract class JsonElement {
|
||||||
/**
|
/**
|
||||||
* @deprecated Creating custom {@code JsonElement} subclasses is highly discouraged
|
* @deprecated Creating custom {@code JsonElement} subclasses is highly discouraged and can lead
|
||||||
* and can lead to undefined behavior.<br>
|
* to undefined behavior.<br>
|
||||||
* This constructor is only kept for backward compatibility.
|
* This constructor is only kept for backward compatibility.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public JsonElement() {
|
public JsonElement() {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a deep copy of this element. Immutable elements like primitives
|
* Returns a deep copy of this element. Immutable elements like primitives and nulls are not
|
||||||
* and nulls are not copied.
|
* copied.
|
||||||
*
|
*
|
||||||
* @since 2.8.2
|
* @since 2.8.2
|
||||||
*/
|
*/
|
||||||
|
@ -102,10 +101,9 @@ public abstract class JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this element as a {@link JsonArray}. If this element is of some
|
* Convenience method to get this element as a {@link JsonArray}. If this element is of some other
|
||||||
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
|
* type, an {@link IllegalStateException} will result. Hence it is best to use this method after
|
||||||
* after ensuring that this element is of the desired type by calling {@link #isJsonArray()}
|
* ensuring that this element is of the desired type by calling {@link #isJsonArray()} first.
|
||||||
* first.
|
|
||||||
*
|
*
|
||||||
* @return this element as a {@link JsonArray}.
|
* @return this element as a {@link JsonArray}.
|
||||||
* @throws IllegalStateException if this element is of another type.
|
* @throws IllegalStateException if this element is of another type.
|
||||||
|
@ -134,10 +132,9 @@ public abstract class JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this element as a {@link JsonNull}. If this element is of some
|
* Convenience method to get this element as a {@link JsonNull}. If this element is of some other
|
||||||
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
|
* type, an {@link IllegalStateException} will result. Hence it is best to use this method after
|
||||||
* after ensuring that this element is of the desired type by calling {@link #isJsonNull()}
|
* ensuring that this element is of the desired type by calling {@link #isJsonNull()} first.
|
||||||
* first.
|
|
||||||
*
|
*
|
||||||
* @return this element as a {@link JsonNull}.
|
* @return this element as a {@link JsonNull}.
|
||||||
* @throws IllegalStateException if this element is of another type.
|
* @throws IllegalStateException if this element is of another type.
|
||||||
|
@ -154,9 +151,10 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a boolean value.
|
* Convenience method to get this element as a boolean value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive boolean value.
|
* @return this element as a primitive boolean value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public boolean getAsBoolean() {
|
public boolean getAsBoolean() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -166,10 +164,10 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a {@link Number}.
|
* Convenience method to get this element as a {@link Number}.
|
||||||
*
|
*
|
||||||
* @return this element as a {@link Number}.
|
* @return this element as a {@link Number}.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray},
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
* or cannot be converted to a number.
|
* JsonArray}, or cannot be converted to a number.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public Number getAsNumber() {
|
public Number getAsNumber() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -179,9 +177,10 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a string value.
|
* Convenience method to get this element as a string value.
|
||||||
*
|
*
|
||||||
* @return this element as a string value.
|
* @return this element as a string value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public String getAsString() {
|
public String getAsString() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -191,10 +190,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a primitive double value.
|
* Convenience method to get this element as a primitive double value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive double value.
|
* @return this element as a primitive double value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if the value contained is not a valid double.
|
* @throws NumberFormatException if the value contained is not a valid double.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public double getAsDouble() {
|
public double getAsDouble() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -204,10 +204,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a primitive float value.
|
* Convenience method to get this element as a primitive float value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive float value.
|
* @return this element as a primitive float value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if the value contained is not a valid float.
|
* @throws NumberFormatException if the value contained is not a valid float.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public float getAsFloat() {
|
public float getAsFloat() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -217,10 +218,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a primitive long value.
|
* Convenience method to get this element as a primitive long value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive long value.
|
* @return this element as a primitive long value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if the value contained is not a valid long.
|
* @throws NumberFormatException if the value contained is not a valid long.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public long getAsLong() {
|
public long getAsLong() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -230,10 +232,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a primitive integer value.
|
* Convenience method to get this element as a primitive integer value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive integer value.
|
* @return this element as a primitive integer value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if the value contained is not a valid integer.
|
* @throws NumberFormatException if the value contained is not a valid integer.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public int getAsInt() {
|
public int getAsInt() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
|
@ -243,10 +246,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a primitive byte value.
|
* Convenience method to get this element as a primitive byte value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive byte value.
|
* @return this element as a primitive byte value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if the value contained is not a valid byte.
|
* @throws NumberFormatException if the value contained is not a valid byte.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
*/
|
*/
|
||||||
public byte getAsByte() {
|
public byte getAsByte() {
|
||||||
|
@ -257,13 +261,13 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get the first character of the string value of this element.
|
* Convenience method to get the first character of the string value of this element.
|
||||||
*
|
*
|
||||||
* @return the first character of the string value.
|
* @return the first character of the string value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray},
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
* or if its string value is empty.
|
* JsonArray}, or if its string value is empty.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
* @deprecated This method is misleading, as it does not get this element as a char but rather as
|
* @deprecated This method is misleading, as it does not get this element as a char but rather as
|
||||||
* a string's first character.
|
* a string's first character.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public char getAsCharacter() {
|
public char getAsCharacter() {
|
||||||
|
@ -274,10 +278,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a {@link BigDecimal}.
|
* Convenience method to get this element as a {@link BigDecimal}.
|
||||||
*
|
*
|
||||||
* @return this element as a {@link BigDecimal}.
|
* @return this element as a {@link BigDecimal}.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if this element is not a valid {@link BigDecimal}.
|
* @throws NumberFormatException if this element is not a valid {@link BigDecimal}.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public BigDecimal getAsBigDecimal() {
|
public BigDecimal getAsBigDecimal() {
|
||||||
|
@ -288,10 +293,11 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a {@link BigInteger}.
|
* Convenience method to get this element as a {@link BigInteger}.
|
||||||
*
|
*
|
||||||
* @return this element as a {@link BigInteger}.
|
* @return this element as a {@link BigInteger}.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if this element is not a valid {@link BigInteger}.
|
* @throws NumberFormatException if this element is not a valid {@link BigInteger}.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public BigInteger getAsBigInteger() {
|
public BigInteger getAsBigInteger() {
|
||||||
|
@ -302,24 +308,24 @@ public abstract class JsonElement {
|
||||||
* Convenience method to get this element as a primitive short value.
|
* Convenience method to get this element as a primitive short value.
|
||||||
*
|
*
|
||||||
* @return this element as a primitive short value.
|
* @return this element as a primitive short value.
|
||||||
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray}.
|
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link
|
||||||
|
* JsonArray}.
|
||||||
* @throws NumberFormatException if the value contained is not a valid short.
|
* @throws NumberFormatException if the value contained is not a valid short.
|
||||||
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
|
||||||
* more than a single element.
|
* more than a single element.
|
||||||
*/
|
*/
|
||||||
public short getAsShort() {
|
public short getAsShort() {
|
||||||
throw new UnsupportedOperationException(getClass().getSimpleName());
|
throw new UnsupportedOperationException(getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns a String representation of this element. */
|
||||||
* Returns a String representation of this element.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
try {
|
try {
|
||||||
StringWriter stringWriter = new StringWriter();
|
StringWriter stringWriter = new StringWriter();
|
||||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||||
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive contains NaN
|
// Make writer lenient because toString() must not fail, even if for example JsonPrimitive
|
||||||
|
// contains NaN
|
||||||
jsonWriter.setStrictness(Strictness.LENIENT);
|
jsonWriter.setStrictness(Strictness.LENIENT);
|
||||||
Streams.write(this, jsonWriter);
|
Streams.write(this, jsonWriter);
|
||||||
return stringWriter.toString();
|
return stringWriter.toString();
|
||||||
|
|
|
@ -16,9 +16,8 @@
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exception is raised when Gson was unable to read an input stream
|
* This exception is raised when Gson was unable to read an input stream or write to one.
|
||||||
* or write to one.
|
*
|
||||||
*
|
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
|
@ -34,8 +33,8 @@ public final class JsonIOException extends JsonParseException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates exception with the specified cause. Consider using
|
* Creates exception with the specified cause. Consider using {@link #JsonIOException(String,
|
||||||
* {@link #JsonIOException(String, Throwable)} instead if you can describe what happened.
|
* Throwable)} instead if you can describe what happened.
|
||||||
*
|
*
|
||||||
* @param cause root exception that caused this exception to be thrown.
|
* @param cause root exception that caused this exception to be thrown.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,69 +1,65 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2008 Google Inc.
|
* Copyright (C) 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class representing a JSON {@code null} value.
|
* A class representing a JSON {@code null} value.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
* @since 1.2
|
* @since 1.2
|
||||||
*/
|
*/
|
||||||
public final class JsonNull extends JsonElement {
|
public final class JsonNull extends JsonElement {
|
||||||
/**
|
/**
|
||||||
* Singleton for {@code JsonNull}.
|
* Singleton for {@code JsonNull}.
|
||||||
*
|
*
|
||||||
* @since 1.8
|
* @since 1.8
|
||||||
*/
|
*/
|
||||||
public static final JsonNull INSTANCE = new JsonNull();
|
public static final JsonNull INSTANCE = new JsonNull();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code JsonNull} object.
|
* Creates a new {@code JsonNull} object.
|
||||||
*
|
*
|
||||||
* @deprecated Deprecated since Gson version 1.8, use {@link #INSTANCE} instead.
|
* @deprecated Deprecated since Gson version 1.8, use {@link #INSTANCE} instead.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public JsonNull() {
|
public JsonNull() {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the same instance since it is an immutable value.
|
* Returns the same instance since it is an immutable value.
|
||||||
*
|
*
|
||||||
* @since 2.8.2
|
* @since 2.8.2
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public JsonNull deepCopy() {
|
public JsonNull deepCopy() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** All instances of {@code JsonNull} have the same hash code since they are indistinguishable. */
|
||||||
* All instances of {@code JsonNull} have the same hash code since they are indistinguishable.
|
@Override
|
||||||
*/
|
public int hashCode() {
|
||||||
@Override
|
return JsonNull.class.hashCode();
|
||||||
public int hashCode() {
|
}
|
||||||
return JsonNull.class.hashCode();
|
|
||||||
}
|
/** All instances of {@code JsonNull} are considered equal. */
|
||||||
|
@Override
|
||||||
/**
|
public boolean equals(Object other) {
|
||||||
* All instances of {@code JsonNull} are considered equal.
|
return other instanceof JsonNull;
|
||||||
*/
|
}
|
||||||
@Override
|
}
|
||||||
public boolean equals(Object other) {
|
|
||||||
return other instanceof JsonNull;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,11 +24,11 @@ import java.util.Set;
|
||||||
* A class representing an object type in Json. An object consists of name-value pairs where names
|
* A class representing an object type in Json. An object consists of name-value pairs where names
|
||||||
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
|
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
|
||||||
* tree of JsonElements. The member elements of this object are maintained in order they were added.
|
* tree of JsonElements. The member elements of this object are maintained in order they were added.
|
||||||
* This class does not support {@code null} values. If {@code null} is provided as value argument
|
* This class does not support {@code null} values. If {@code null} is provided as value argument to
|
||||||
* to any of the methods, it is converted to a {@link JsonNull}.
|
* any of the methods, it is converted to a {@link JsonNull}.
|
||||||
*
|
*
|
||||||
* <p>{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view
|
* <p>{@code JsonObject} does not implement the {@link Map} interface, but a {@code Map} view of it
|
||||||
* of it can be obtained with {@link #asMap()}.
|
* can be obtained with {@link #asMap()}.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -36,12 +36,9 @@ import java.util.Set;
|
||||||
public final class JsonObject extends JsonElement {
|
public final class JsonObject extends JsonElement {
|
||||||
private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>(false);
|
private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>(false);
|
||||||
|
|
||||||
/**
|
/** Creates an empty JsonObject. */
|
||||||
* Creates an empty JsonObject.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation") // superclass constructor
|
@SuppressWarnings("deprecation") // superclass constructor
|
||||||
public JsonObject() {
|
public JsonObject() {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a deep copy of this element and all its children.
|
* Creates a deep copy of this element and all its children.
|
||||||
|
@ -59,8 +56,8 @@ public final class JsonObject extends JsonElement {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a member, which is a name-value pair, to self. The name must be a String, but the value
|
* Adds a member, which is a name-value pair, to self. The name must be a String, but the value
|
||||||
* can be an arbitrary {@link JsonElement}, thereby allowing you to build a full tree of JsonElements
|
* can be an arbitrary {@link JsonElement}, thereby allowing you to build a full tree of
|
||||||
* rooted at this node.
|
* JsonElements rooted at this node.
|
||||||
*
|
*
|
||||||
* @param property name of the member.
|
* @param property name of the member.
|
||||||
* @param value the member object.
|
* @param value the member object.
|
||||||
|
@ -73,8 +70,8 @@ public final class JsonObject extends JsonElement {
|
||||||
* Removes the {@code property} from this object.
|
* Removes the {@code property} from this object.
|
||||||
*
|
*
|
||||||
* @param property name of the member that should be removed.
|
* @param property name of the member that should be removed.
|
||||||
* @return the {@link JsonElement} object that is being removed, or {@code null} if no
|
* @return the {@link JsonElement} object that is being removed, or {@code null} if no member with
|
||||||
* member with this name exists.
|
* this name exists.
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
*/
|
*/
|
||||||
public JsonElement remove(String property) {
|
public JsonElement remove(String property) {
|
||||||
|
@ -82,8 +79,8 @@ public final class JsonObject extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to add a string member. The specified value is converted to a
|
* Convenience method to add a string member. The specified value is converted to a {@link
|
||||||
* {@link JsonPrimitive} of String.
|
* JsonPrimitive} of String.
|
||||||
*
|
*
|
||||||
* @param property name of the member.
|
* @param property name of the member.
|
||||||
* @param value the string value associated with the member.
|
* @param value the string value associated with the member.
|
||||||
|
@ -93,8 +90,8 @@ public final class JsonObject extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to add a number member. The specified value is converted to a
|
* Convenience method to add a number member. The specified value is converted to a {@link
|
||||||
* {@link JsonPrimitive} of Number.
|
* JsonPrimitive} of Number.
|
||||||
*
|
*
|
||||||
* @param property name of the member.
|
* @param property name of the member.
|
||||||
* @param value the number value associated with the member.
|
* @param value the number value associated with the member.
|
||||||
|
@ -104,8 +101,8 @@ public final class JsonObject extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to add a boolean member. The specified value is converted to a
|
* Convenience method to add a boolean member. The specified value is converted to a {@link
|
||||||
* {@link JsonPrimitive} of Boolean.
|
* JsonPrimitive} of Boolean.
|
||||||
*
|
*
|
||||||
* @param property name of the member.
|
* @param property name of the member.
|
||||||
* @param value the boolean value associated with the member.
|
* @param value the boolean value associated with the member.
|
||||||
|
@ -115,8 +112,8 @@ public final class JsonObject extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to add a char member. The specified value is converted to a
|
* Convenience method to add a char member. The specified value is converted to a {@link
|
||||||
* {@link JsonPrimitive} of Character.
|
* JsonPrimitive} of Character.
|
||||||
*
|
*
|
||||||
* @param property name of the member.
|
* @param property name of the member.
|
||||||
* @param value the char value associated with the member.
|
* @param value the char value associated with the member.
|
||||||
|
@ -190,7 +187,7 @@ public final class JsonObject extends JsonElement {
|
||||||
*
|
*
|
||||||
* @param memberName name of the member being requested.
|
* @param memberName name of the member being requested.
|
||||||
* @return the {@code JsonPrimitive} corresponding to the specified member, or {@code null} if no
|
* @return the {@code JsonPrimitive} corresponding to the specified member, or {@code null} if no
|
||||||
* member with this name exists.
|
* member with this name exists.
|
||||||
* @throws ClassCastException if the member is not of type {@code JsonPrimitive}.
|
* @throws ClassCastException if the member is not of type {@code JsonPrimitive}.
|
||||||
*/
|
*/
|
||||||
public JsonPrimitive getAsJsonPrimitive(String memberName) {
|
public JsonPrimitive getAsJsonPrimitive(String memberName) {
|
||||||
|
@ -202,7 +199,7 @@ public final class JsonObject extends JsonElement {
|
||||||
*
|
*
|
||||||
* @param memberName name of the member being requested.
|
* @param memberName name of the member being requested.
|
||||||
* @return the {@code JsonArray} corresponding to the specified member, or {@code null} if no
|
* @return the {@code JsonArray} corresponding to the specified member, or {@code null} if no
|
||||||
* member with this name exists.
|
* member with this name exists.
|
||||||
* @throws ClassCastException if the member is not of type {@code JsonArray}.
|
* @throws ClassCastException if the member is not of type {@code JsonArray}.
|
||||||
*/
|
*/
|
||||||
public JsonArray getAsJsonArray(String memberName) {
|
public JsonArray getAsJsonArray(String memberName) {
|
||||||
|
@ -214,7 +211,7 @@ public final class JsonObject extends JsonElement {
|
||||||
*
|
*
|
||||||
* @param memberName name of the member being requested.
|
* @param memberName name of the member being requested.
|
||||||
* @return the {@code JsonObject} corresponding to the specified member, or {@code null} if no
|
* @return the {@code JsonObject} corresponding to the specified member, or {@code null} if no
|
||||||
* member with this name exists.
|
* member with this name exists.
|
||||||
* @throws ClassCastException if the member is not of type {@code JsonObject}.
|
* @throws ClassCastException if the member is not of type {@code JsonObject}.
|
||||||
*/
|
*/
|
||||||
public JsonObject getAsJsonObject(String memberName) {
|
public JsonObject getAsJsonObject(String memberName) {
|
||||||
|
@ -222,12 +219,12 @@ public final class JsonObject extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a mutable {@link Map} view of this {@code JsonObject}. Changes to the {@code Map}
|
* Returns a mutable {@link Map} view of this {@code JsonObject}. Changes to the {@code Map} are
|
||||||
* are visible in this {@code JsonObject} and the other way around.
|
* visible in this {@code JsonObject} and the other way around.
|
||||||
*
|
*
|
||||||
* <p>The {@code Map} does not permit {@code null} keys or values. Unlike {@code JsonObject}'s
|
* <p>The {@code Map} does not permit {@code null} keys or values. Unlike {@code JsonObject}'s
|
||||||
* {@code null} handling, a {@link NullPointerException} is thrown when trying to add {@code null}.
|
* {@code null} handling, a {@link NullPointerException} is thrown when trying to add {@code
|
||||||
* Use {@link JsonNull} for JSON null values.
|
* null}. Use {@link JsonNull} for JSON null values.
|
||||||
*
|
*
|
||||||
* @return mutable {@code Map} view
|
* @return mutable {@code Map} view
|
||||||
* @since 2.10
|
* @since 2.10
|
||||||
|
@ -238,19 +235,17 @@ public final class JsonObject extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the other object is equal to this. This method only considers
|
* Returns whether the other object is equal to this. This method only considers the other object
|
||||||
* the other object to be equal if it is an instance of {@code JsonObject} and has
|
* to be equal if it is an instance of {@code JsonObject} and has equal members, ignoring order.
|
||||||
* equal members, ignoring order.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
return (o == this) || (o instanceof JsonObject
|
return (o == this) || (o instanceof JsonObject && ((JsonObject) o).members.equals(members));
|
||||||
&& ((JsonObject) o).members.equals(members));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash code of this object. This method calculates the hash code based
|
* Returns the hash code of this object. This method calculates the hash code based on the members
|
||||||
* on the members of this object, ignoring order.
|
* of this object, ignoring order.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exception is raised if there is a serious issue that occurs during parsing of a Json
|
* This exception is raised if there is a serious issue that occurs during parsing of a Json string.
|
||||||
* string. One of the main usages for this class is for the Gson infrastructure. If the incoming
|
* One of the main usages for this class is for the Gson infrastructure. If the incoming Json is
|
||||||
* Json is bad/malicious, an instance of this exception is raised.
|
* bad/malicious, an instance of this exception is raised.
|
||||||
*
|
*
|
||||||
* <p>This exception is a {@link RuntimeException} because it is exposed to the client. Using a
|
* <p>This exception is a {@link RuntimeException} because it is exposed to the client. Using a
|
||||||
* {@link RuntimeException} avoids bad coding practices on the client side where they catch the
|
* {@link RuntimeException} avoids bad coding practices on the client side where they catch the
|
||||||
* exception and do nothing. It is often the case that you want to blow up if there is a parsing
|
* exception and do nothing. It is often the case that you want to blow up if there is a parsing
|
||||||
* error (i.e. often clients do not know how to recover from a {@link JsonParseException}.</p>
|
* error (i.e. often clients do not know how to recover from a {@link JsonParseException}.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -53,8 +53,8 @@ public class JsonParseException extends RuntimeException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates exception with the specified cause. Consider using
|
* Creates exception with the specified cause. Consider using {@link #JsonParseException(String,
|
||||||
* {@link #JsonParseException(String, Throwable)} instead if you can describe what happened.
|
* Throwable)} instead if you can describe what happened.
|
||||||
*
|
*
|
||||||
* @param cause root exception that caused this exception to be thrown.
|
* @param cause root exception that caused this exception to be thrown.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,129 +1,135 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2009 Google Inc.
|
* Copyright (C) 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
import com.google.gson.internal.Streams;
|
import com.google.gson.internal.Streams;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.MalformedJsonException;
|
import com.google.gson.stream.MalformedJsonException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parser to parse JSON into a parse tree of {@link JsonElement}s.
|
* A parser to parse JSON into a parse tree of {@link JsonElement}s.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
*/
|
*/
|
||||||
public final class JsonParser {
|
public final class JsonParser {
|
||||||
/** @deprecated No need to instantiate this class, use the static methods instead. */
|
/**
|
||||||
@Deprecated
|
* @deprecated No need to instantiate this class, use the static methods instead.
|
||||||
public JsonParser() {}
|
*/
|
||||||
|
@Deprecated
|
||||||
/**
|
public JsonParser() {}
|
||||||
* Parses the specified JSON string into a parse tree.
|
|
||||||
* An exception is thrown if the JSON string has multiple top-level JSON elements,
|
/**
|
||||||
* or if there is trailing data.
|
* Parses the specified JSON string into a parse tree. 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#setStrictness(Strictness) 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
|
* @param json JSON text
|
||||||
* @throws JsonParseException if the specified text is not valid JSON
|
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
|
||||||
* @since 2.8.6
|
* @throws JsonParseException if the specified text is not valid JSON
|
||||||
*/
|
* @since 2.8.6
|
||||||
public static JsonElement parseString(String json) throws JsonSyntaxException {
|
*/
|
||||||
return parseReader(new StringReader(json));
|
public static JsonElement parseString(String json) throws JsonSyntaxException {
|
||||||
}
|
return parseReader(new StringReader(json));
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Parses the complete JSON string provided by the reader into a parse tree.
|
/**
|
||||||
* An exception is thrown if the JSON string has multiple top-level JSON elements,
|
* Parses the complete JSON string provided by the reader into a parse tree. An exception is
|
||||||
* or if there is trailing data.
|
* 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#setStrictness(Strictness) lenient mode}.
|
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode}.
|
||||||
*
|
*
|
||||||
* @param reader JSON text
|
* @param reader JSON text
|
||||||
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
|
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
|
||||||
* @throws JsonParseException if there is an IOException or if the specified
|
* @throws JsonParseException if there is an IOException or if the specified text is not valid
|
||||||
* text is not valid JSON
|
* JSON
|
||||||
* @since 2.8.6
|
* @since 2.8.6
|
||||||
*/
|
*/
|
||||||
public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
|
public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
|
||||||
try {
|
try {
|
||||||
JsonReader jsonReader = new JsonReader(reader);
|
JsonReader jsonReader = new JsonReader(reader);
|
||||||
JsonElement element = parseReader(jsonReader);
|
JsonElement element = parseReader(jsonReader);
|
||||||
if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) {
|
if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) {
|
||||||
throw new JsonSyntaxException("Did not consume the entire document.");
|
throw new JsonSyntaxException("Did not consume the entire document.");
|
||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
} catch (MalformedJsonException e) {
|
} catch (MalformedJsonException e) {
|
||||||
throw new JsonSyntaxException(e);
|
throw new JsonSyntaxException(e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new JsonIOException(e);
|
throw new JsonIOException(e);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new JsonSyntaxException(e);
|
throw new JsonSyntaxException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next value from the JSON stream as a parse tree.
|
* Returns the next value from the JSON stream as a parse tree. Unlike the other {@code parse}
|
||||||
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has
|
* methods, no exception is thrown if the JSON data has multiple top-level JSON elements, or if
|
||||||
* multiple top-level JSON elements, or if there is trailing data.
|
* there is trailing data.
|
||||||
*
|
*
|
||||||
* <p>The JSON data is parsed in {@linkplain JsonReader#setStrictness(Strictness) lenient mode},
|
* <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
|
* regardless of the strictness setting of the provided reader. The strictness setting of the
|
||||||
* of the reader is restored once this method returns.
|
* reader is restored once this method returns.
|
||||||
*
|
*
|
||||||
* @throws JsonParseException if there is an IOException or if the specified
|
* @throws JsonParseException if there is an IOException or if the specified text is not valid
|
||||||
* text is not valid JSON
|
* JSON
|
||||||
* @since 2.8.6
|
* @since 2.8.6
|
||||||
*/
|
*/
|
||||||
public static JsonElement parseReader(JsonReader reader)
|
public static JsonElement parseReader(JsonReader reader)
|
||||||
throws JsonIOException, JsonSyntaxException {
|
throws JsonIOException, JsonSyntaxException {
|
||||||
Strictness strictness = reader.getStrictness();
|
Strictness strictness = reader.getStrictness();
|
||||||
reader.setStrictness(Strictness.LENIENT);
|
reader.setStrictness(Strictness.LENIENT);
|
||||||
try {
|
try {
|
||||||
return Streams.parse(reader);
|
return Streams.parse(reader);
|
||||||
} catch (StackOverflowError e) {
|
} catch (StackOverflowError e) {
|
||||||
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
|
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
|
throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
|
||||||
} finally {
|
} finally {
|
||||||
reader.setStrictness(strictness);
|
reader.setStrictness(strictness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link JsonParser#parseString} */
|
/**
|
||||||
@Deprecated
|
* @deprecated Use {@link JsonParser#parseString}
|
||||||
public JsonElement parse(String json) throws JsonSyntaxException {
|
*/
|
||||||
return parseString(json);
|
@Deprecated
|
||||||
}
|
public JsonElement parse(String json) throws JsonSyntaxException {
|
||||||
|
return parseString(json);
|
||||||
/** @deprecated Use {@link JsonParser#parseReader(Reader)} */
|
}
|
||||||
@Deprecated
|
|
||||||
public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException {
|
/**
|
||||||
return parseReader(json);
|
* @deprecated Use {@link JsonParser#parseReader(Reader)}
|
||||||
}
|
*/
|
||||||
|
@Deprecated
|
||||||
/** @deprecated Use {@link JsonParser#parseReader(JsonReader)} */
|
public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException {
|
||||||
@Deprecated
|
return parseReader(json);
|
||||||
public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {
|
}
|
||||||
return parseReader(json);
|
|
||||||
}
|
/**
|
||||||
}
|
* @deprecated Use {@link JsonParser#parseReader(JsonReader)}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {
|
||||||
|
return parseReader(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,14 +17,14 @@
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
import com.google.gson.internal.LazilyParsedNumber;
|
import com.google.gson.internal.LazilyParsedNumber;
|
||||||
|
import com.google.gson.internal.NumberLimits;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class representing a JSON primitive value. A primitive value
|
* A class representing a JSON primitive value. A primitive value is either a String, a Java
|
||||||
* is either a String, a Java primitive, or a Java primitive
|
* primitive, or a Java primitive wrapper type.
|
||||||
* wrapper type.
|
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -38,7 +38,10 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
*
|
*
|
||||||
* @param bool the value to create the primitive with.
|
* @param bool the value to create the primitive with.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation") // superclass constructor
|
// "deprecation" suppression for superclass constructor
|
||||||
|
// "UnnecessaryBoxedVariable" Error Prone warning is correct since method does not accept
|
||||||
|
// null, but cannot be changed anymore since this is public API
|
||||||
|
@SuppressWarnings({"deprecation", "UnnecessaryBoxedVariable"})
|
||||||
public JsonPrimitive(Boolean bool) {
|
public JsonPrimitive(Boolean bool) {
|
||||||
value = Objects.requireNonNull(bool);
|
value = Objects.requireNonNull(bool);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +72,10 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
*
|
*
|
||||||
* @param c the value to create the primitive with.
|
* @param c the value to create the primitive with.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation") // superclass constructor
|
// "deprecation" suppression for superclass constructor
|
||||||
|
// "UnnecessaryBoxedVariable" Error Prone warning is correct since method does not accept
|
||||||
|
// null, but cannot be changed anymore since this is public API
|
||||||
|
@SuppressWarnings({"deprecation", "UnnecessaryBoxedVariable"})
|
||||||
public JsonPrimitive(Character c) {
|
public JsonPrimitive(Character c) {
|
||||||
// convert characters to strings since in JSON, characters are represented as a single
|
// convert characters to strings since in JSON, characters are represented as a single
|
||||||
// character string
|
// character string
|
||||||
|
@ -96,10 +102,10 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this element as a boolean value.
|
* Convenience method to get this element as a boolean value. If this primitive {@linkplain
|
||||||
* If this primitive {@linkplain #isBoolean() is not a boolean}, the string value
|
* #isBoolean() is not a boolean}, the string value is parsed using {@link
|
||||||
* is parsed using {@link Boolean#parseBoolean(String)}. This means {@code "true"} (ignoring
|
* Boolean#parseBoolean(String)}. This means {@code "true"} (ignoring case) is considered {@code
|
||||||
* case) is considered {@code true} and any other value is considered {@code false}.
|
* true} and any other value is considered {@code false}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean getAsBoolean() {
|
public boolean getAsBoolean() {
|
||||||
|
@ -120,10 +126,9 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to get this element as a {@link Number}.
|
* Convenience method to get this element as a {@link Number}. If this primitive {@linkplain
|
||||||
* If this primitive {@linkplain #isString() is a string}, a lazily parsed {@code Number}
|
* #isString() is a string}, a lazily parsed {@code Number} is constructed which parses the string
|
||||||
* is constructed which parses the string when any of its methods are called (which can
|
* when any of its methods are called (which can lead to a {@link NumberFormatException}).
|
||||||
* lead to a {@link NumberFormatException}).
|
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException if this primitive is neither a number nor a string.
|
* @throws UnsupportedOperationException if this primitive is neither a number nor a string.
|
||||||
*/
|
*/
|
||||||
|
@ -172,7 +177,9 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public BigDecimal getAsBigDecimal() {
|
public BigDecimal getAsBigDecimal() {
|
||||||
return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(getAsString());
|
return value instanceof BigDecimal
|
||||||
|
? (BigDecimal) value
|
||||||
|
: NumberLimits.parseBigDecimal(getAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,7 +191,7 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
? (BigInteger) value
|
? (BigInteger) value
|
||||||
: isIntegral(this)
|
: isIntegral(this)
|
||||||
? BigInteger.valueOf(this.getAsNumber().longValue())
|
? BigInteger.valueOf(this.getAsNumber().longValue())
|
||||||
: new BigInteger(this.getAsString());
|
: NumberLimits.parseBigInteger(this.getAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,9 +221,9 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
return isNumber() ? getAsNumber().shortValue() : Short.parseShort(getAsString());
|
return isNumber() ? getAsNumber().shortValue() : Short.parseShort(getAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws NumberFormatException {@inheritDoc}
|
* @throws NumberFormatException {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int getAsInt() {
|
public int getAsInt() {
|
||||||
return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString());
|
return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString());
|
||||||
|
@ -231,10 +238,9 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws UnsupportedOperationException if the string value of this
|
* @throws UnsupportedOperationException if the string value of this primitive is empty.
|
||||||
* primitive is empty.
|
|
||||||
* @deprecated This method is misleading, as it does not get this element as a char but rather as
|
* @deprecated This method is misleading, as it does not get this element as a char but rather as
|
||||||
* a string's first character.
|
* a string's first character.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
|
@ -247,9 +253,7 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the hash code of this object. */
|
||||||
* Returns the hash code of this object.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -268,9 +272,8 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the other object is equal to this. This method only considers
|
* Returns whether the other object is equal to this. This method only considers the other object
|
||||||
* the other object to be equal if it is an instance of {@code JsonPrimitive} and
|
* to be equal if it is an instance of {@code JsonPrimitive} and has an equal value.
|
||||||
* has an equal value.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
|
@ -280,34 +283,42 @@ public final class JsonPrimitive extends JsonElement {
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
JsonPrimitive other = (JsonPrimitive)obj;
|
JsonPrimitive other = (JsonPrimitive) obj;
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return other.value == null;
|
return other.value == null;
|
||||||
}
|
}
|
||||||
if (isIntegral(this) && isIntegral(other)) {
|
if (isIntegral(this) && isIntegral(other)) {
|
||||||
return this.value instanceof BigInteger || other.value instanceof BigInteger
|
return (this.value instanceof BigInteger || other.value instanceof BigInteger)
|
||||||
? this.getAsBigInteger().equals(other.getAsBigInteger())
|
? this.getAsBigInteger().equals(other.getAsBigInteger())
|
||||||
: this.getAsNumber().longValue() == other.getAsNumber().longValue();
|
: this.getAsNumber().longValue() == other.getAsNumber().longValue();
|
||||||
}
|
}
|
||||||
if (value instanceof Number && other.value instanceof Number) {
|
if (value instanceof Number && other.value instanceof Number) {
|
||||||
double a = getAsNumber().doubleValue();
|
if (value instanceof BigDecimal && other.value instanceof BigDecimal) {
|
||||||
// Java standard types other than double return true for two NaN. So, need
|
// Uses compareTo to ignore scale of values, e.g. `0` and `0.00` should be considered equal
|
||||||
// special handling for double.
|
return this.getAsBigDecimal().compareTo(other.getAsBigDecimal()) == 0;
|
||||||
double b = other.getAsNumber().doubleValue();
|
}
|
||||||
return a == b || (Double.isNaN(a) && Double.isNaN(b));
|
|
||||||
|
double thisAsDouble = this.getAsDouble();
|
||||||
|
double otherAsDouble = other.getAsDouble();
|
||||||
|
// Don't use Double.compare(double, double) because that considers -0.0 and +0.0 not equal
|
||||||
|
return (thisAsDouble == otherAsDouble)
|
||||||
|
|| (Double.isNaN(thisAsDouble) && Double.isNaN(otherAsDouble));
|
||||||
}
|
}
|
||||||
return value.equals(other.value);
|
return value.equals(other.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the specified number is an integral type
|
* Returns true if the specified number is an integral type (Long, Integer, Short, Byte,
|
||||||
* (Long, Integer, Short, Byte, BigInteger)
|
* BigInteger)
|
||||||
*/
|
*/
|
||||||
private static boolean isIntegral(JsonPrimitive primitive) {
|
private static boolean isIntegral(JsonPrimitive primitive) {
|
||||||
if (primitive.value instanceof Number) {
|
if (primitive.value instanceof Number) {
|
||||||
Number number = (Number) primitive.value;
|
Number number = (Number) primitive.value;
|
||||||
return number instanceof BigInteger || number instanceof Long || number instanceof Integer
|
return number instanceof BigInteger
|
||||||
|| number instanceof Short || number instanceof Byte;
|
|| number instanceof Long
|
||||||
|
|| number instanceof Integer
|
||||||
|
|| number instanceof Short
|
||||||
|
|| number instanceof Byte;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,8 @@ package com.google.gson;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context for serialization that is passed to a custom serializer during invocation of its
|
* Context for serialization that is passed to a custom serializer during invocation of its {@link
|
||||||
* {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method.
|
* JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -36,10 +36,10 @@ public interface JsonSerializationContext {
|
||||||
public JsonElement serialize(Object src);
|
public JsonElement serialize(Object src);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes default serialization on the specified object passing the specific type information.
|
* Invokes default serialization on the specified object passing the specific type information. It
|
||||||
* It should never be invoked on the element received as a parameter of the
|
* should never be invoked on the element received as a parameter of the {@link
|
||||||
* {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing
|
* JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing so will result
|
||||||
* so will result in an infinite loop since Gson will in-turn call the custom serializer again.
|
* in an infinite loop since Gson will in-turn call the custom serializer again.
|
||||||
*
|
*
|
||||||
* @param src the object that needs to be serialized.
|
* @param src the object that needs to be serialized.
|
||||||
* @param typeOfSrc the actual genericized type of src object.
|
* @param typeOfSrc the actual genericized type of src object.
|
||||||
|
|
|
@ -19,12 +19,12 @@ package com.google.gson;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface representing a custom serializer for JSON. You should write a custom serializer, if
|
* Interface representing a custom serializer for JSON. You should write a custom serializer, if you
|
||||||
* you are not happy with the default serialization done by Gson. You will also need to register
|
* are not happy with the default serialization done by Gson. You will also need to register this
|
||||||
* this serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}.
|
* serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}.
|
||||||
*
|
*
|
||||||
* <p>Let us look at example where defining a serializer will be useful. The {@code Id} class
|
* <p>Let us look at example where defining a serializer will be useful. The {@code Id} class
|
||||||
* defined below has two fields: {@code clazz} and {@code value}.</p>
|
* defined below has two fields: {@code clazz} and {@code value}.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class Id<T> {
|
* public class Id<T> {
|
||||||
|
@ -42,10 +42,9 @@ import java.lang.reflect.Type;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be
|
* <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be <code>
|
||||||
* <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you just want the output to be
|
* {"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you just want the output to be the value
|
||||||
* the value instead, which is {@code 20} in this case. You can achieve that by writing a custom
|
* instead, which is {@code 20} in this case. You can achieve that by writing a custom serializer:
|
||||||
* serializer:</p>
|
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* class IdSerializer implements JsonSerializer<Id> {
|
* class IdSerializer implements JsonSerializer<Id> {
|
||||||
|
@ -55,22 +54,22 @@ import java.lang.reflect.Type;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>You will also need to register {@code IdSerializer} with Gson as follows:</p>
|
* <p>You will also need to register {@code IdSerializer} with Gson as follows:
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
|
* Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>Serializers should be stateless and thread-safe, otherwise the thread-safety
|
* <p>Serializers should be stateless and thread-safe, otherwise the thread-safety guarantees of
|
||||||
* guarantees of {@link Gson} might not apply.
|
* {@link Gson} might not apply.
|
||||||
*
|
*
|
||||||
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API
|
* <p>New applications should prefer {@link TypeAdapter}, whose streaming API is more efficient than
|
||||||
* is more efficient than this interface's tree API.
|
* this interface's tree API.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*
|
|
||||||
* @param <T> type for which the serializer is being registered. It is possible that a serializer
|
* @param <T> type for which the serializer is being registered. It is possible that a serializer
|
||||||
* may be asked to serialize a specific generic type of the T.
|
* may be asked to serialize a specific generic type of the T.
|
||||||
*/
|
*/
|
||||||
public interface JsonSerializer<T> {
|
public interface JsonSerializer<T> {
|
||||||
|
|
||||||
|
@ -78,11 +77,11 @@ public interface JsonSerializer<T> {
|
||||||
* Gson invokes this call-back method during serialization when it encounters a field of the
|
* Gson invokes this call-back method during serialization when it encounters a field of the
|
||||||
* specified type.
|
* specified type.
|
||||||
*
|
*
|
||||||
* <p>In the implementation of this call-back method, you should consider invoking
|
* <p>In the implementation of this call-back method, you should consider invoking {@link
|
||||||
* {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
|
* JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
|
||||||
* non-trivial field of the {@code src} object. However, you should never invoke it on the
|
* non-trivial field of the {@code src} object. However, you should never invoke it on the {@code
|
||||||
* {@code src} object itself since that will cause an infinite loop (Gson will call your
|
* src} object itself since that will cause an infinite loop (Gson will call your call-back method
|
||||||
* call-back method again).</p>
|
* again).
|
||||||
*
|
*
|
||||||
* @param src the object that needs to be converted to Json.
|
* @param src the object that needs to be converted to Json.
|
||||||
* @param typeOfSrc the actual type (fully genericized version) of the source object.
|
* @param typeOfSrc the actual type (fully genericized version) of the source object.
|
||||||
|
|
|
@ -27,8 +27,8 @@ import java.util.NoSuchElementException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
|
* 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
|
* asynchronously. The JSON data is parsed in lenient mode, see also {@link
|
||||||
* {@link JsonReader#setStrictness(Strictness)}.
|
* JsonReader#setStrictness(Strictness)}.
|
||||||
*
|
*
|
||||||
* <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To
|
* <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
|
* properly use this class across multiple threads, you will need to add some external
|
||||||
|
@ -71,8 +71,8 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next available {@link JsonElement} on the reader. Throws a
|
* Returns the next available {@link JsonElement} on the reader. Throws a {@link
|
||||||
* {@link NoSuchElementException} if no element is available.
|
* NoSuchElementException} if no element is available.
|
||||||
*
|
*
|
||||||
* @return the next available {@code JsonElement} on the reader.
|
* @return the next available {@code JsonElement} on the reader.
|
||||||
* @throws JsonParseException if the incoming stream is malformed JSON.
|
* @throws JsonParseException if the incoming stream is malformed JSON.
|
||||||
|
@ -96,6 +96,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if a {@link JsonElement} is available on the input for consumption
|
* Returns true if a {@link JsonElement} is available on the input for consumption
|
||||||
|
*
|
||||||
* @return true if a {@link JsonElement} is available on the input, false otherwise
|
* @return true if a {@link JsonElement} is available on the input, false otherwise
|
||||||
* @throws JsonParseException if the incoming stream is malformed JSON.
|
* @throws JsonParseException if the incoming stream is malformed JSON.
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
|
@ -116,6 +117,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
|
||||||
/**
|
/**
|
||||||
* This optional {@link Iterator} method is not relevant for stream parsing and hence is not
|
* This optional {@link Iterator} method is not relevant for stream parsing and hence is not
|
||||||
* implemented.
|
* implemented.
|
||||||
|
*
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exception is raised when Gson attempts to read (or write) a malformed
|
* This exception is raised when Gson attempts to read (or write) a malformed JSON element.
|
||||||
* JSON element.
|
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -35,9 +34,8 @@ public final class JsonSyntaxException extends JsonParseException {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates exception with the specified cause. Consider using
|
* Creates exception with the specified cause. Consider using {@link #JsonSyntaxException(String,
|
||||||
* {@link #JsonSyntaxException(String, Throwable)} instead if you can
|
* Throwable)} instead if you can describe what actually happened.
|
||||||
* describe what actually happened.
|
|
||||||
*
|
*
|
||||||
* @param cause root exception that caused this exception to be thrown.
|
* @param cause root exception that caused this exception to be thrown.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,7 +20,6 @@ package com.google.gson;
|
||||||
* Defines the expected format for a {@code long} or {@code Long} type when it is serialized.
|
* Defines the expected format for a {@code long} or {@code Long} type when it is serialized.
|
||||||
*
|
*
|
||||||
* @since 1.3
|
* @since 1.3
|
||||||
*
|
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
|
@ -28,36 +27,36 @@ public enum LongSerializationPolicy {
|
||||||
/**
|
/**
|
||||||
* This is the "default" serialization policy that will output a {@code Long} object as a JSON
|
* This is the "default" serialization policy that will output a {@code Long} object as a JSON
|
||||||
* number. For example, assume an object has a long field named "f" then the serialized output
|
* number. For example, assume an object has a long field named "f" then the serialized output
|
||||||
* would be:
|
* would be: {@code {"f":123}}
|
||||||
* {@code {"f":123}}
|
|
||||||
*
|
*
|
||||||
* <p>A {@code null} value is serialized as {@link JsonNull}.
|
* <p>A {@code null} value is serialized as {@link JsonNull}.
|
||||||
*/
|
*/
|
||||||
DEFAULT() {
|
DEFAULT() {
|
||||||
@Override public JsonElement serialize(Long value) {
|
@Override
|
||||||
|
public JsonElement serialize(Long value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return JsonNull.INSTANCE;
|
return JsonNull.INSTANCE;
|
||||||
}
|
}
|
||||||
return new JsonPrimitive(value);
|
return new JsonPrimitive(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes a long value as a quoted string. For example, assume an object has a long field
|
* Serializes a long value as a quoted string. For example, assume an object has a long field
|
||||||
* named "f" then the serialized output would be:
|
* named "f" then the serialized output would be: {@code {"f":"123"}}
|
||||||
* {@code {"f":"123"}}
|
|
||||||
*
|
*
|
||||||
* <p>A {@code null} value is serialized as {@link JsonNull}.
|
* <p>A {@code null} value is serialized as {@link JsonNull}.
|
||||||
*/
|
*/
|
||||||
STRING() {
|
STRING() {
|
||||||
@Override public JsonElement serialize(Long value) {
|
@Override
|
||||||
|
public JsonElement serialize(Long value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return JsonNull.INSTANCE;
|
return JsonNull.INSTANCE;
|
||||||
}
|
}
|
||||||
return new JsonPrimitive(value.toString());
|
return new JsonPrimitive(value.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize this {@code value} using this serialization policy.
|
* Serialize this {@code value} using this serialization policy.
|
||||||
*
|
*
|
||||||
|
|
|
@ -20,27 +20,24 @@ import com.google.gson.internal.ReflectionAccessFilterHelper;
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.AccessibleObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter for determining whether reflection based serialization and
|
* Filter for determining whether reflection based serialization and deserialization is allowed for
|
||||||
* deserialization is allowed for a class.
|
* a class.
|
||||||
*
|
*
|
||||||
* <p>A filter can be useful in multiple scenarios, for example when
|
* <p>A filter can be useful in multiple scenarios, for example when upgrading to newer Java
|
||||||
* upgrading to newer Java versions which use the Java Platform Module
|
* versions which use the Java Platform Module System (JPMS). A filter then allows to {@linkplain
|
||||||
* System (JPMS). A filter then allows to {@linkplain FilterResult#BLOCK_INACCESSIBLE
|
* FilterResult#BLOCK_INACCESSIBLE prevent making inaccessible members accessible}, even if the used
|
||||||
* prevent making inaccessible members accessible}, even if the used
|
* Java version might still allow illegal access (but logs a warning), or if {@code java} command
|
||||||
* Java version might still allow illegal access (but logs a warning),
|
* line arguments are used to open the inaccessible packages to other parts of the application. This
|
||||||
* or if {@code java} command line arguments are used to open the inaccessible
|
* interface defines some convenience filters for this task, such as {@link
|
||||||
* packages to other parts of the application. This interface defines some
|
* #BLOCK_INACCESSIBLE_JAVA}.
|
||||||
* convenience filters for this task, such as {@link #BLOCK_INACCESSIBLE_JAVA}.
|
|
||||||
*
|
*
|
||||||
* <p>A filter can also be useful to prevent mixing model classes of a
|
* <p>A filter can also be useful to prevent mixing model classes of a project with other non-model
|
||||||
* project with other non-model classes; the filter could
|
* classes; the filter could {@linkplain FilterResult#BLOCK_ALL block all reflective access} to
|
||||||
* {@linkplain FilterResult#BLOCK_ALL block all reflective access} to
|
|
||||||
* non-model classes.
|
* non-model classes.
|
||||||
*
|
*
|
||||||
* <p>A reflection access filter is similar to an {@link ExclusionStrategy}
|
* <p>A reflection access filter is similar to an {@link ExclusionStrategy} with the major
|
||||||
* with the major difference that a filter will cause an exception to be
|
* difference that a filter will cause an exception to be thrown when access is disallowed while an
|
||||||
* thrown when access is disallowed while an exclusion strategy just skips
|
* exclusion strategy just skips fields and classes.
|
||||||
* fields and classes.
|
|
||||||
*
|
*
|
||||||
* @see GsonBuilder#addReflectionAccessFilter(ReflectionAccessFilter)
|
* @see GsonBuilder#addReflectionAccessFilter(ReflectionAccessFilter)
|
||||||
* @since 2.9.1
|
* @since 2.9.1
|
||||||
|
@ -55,158 +52,177 @@ public interface ReflectionAccessFilter {
|
||||||
/**
|
/**
|
||||||
* Reflection access for the class is allowed.
|
* Reflection access for the class is allowed.
|
||||||
*
|
*
|
||||||
* <p>Note that this does not affect the Java access checks in any way,
|
* <p>Note that this does not affect the Java access checks in any way, it only permits Gson to
|
||||||
* it only permits Gson to try using reflection for a class. The Java
|
* try using reflection for a class. The Java runtime might still deny such access.
|
||||||
* runtime might still deny such access.
|
|
||||||
*/
|
*/
|
||||||
ALLOW,
|
ALLOW,
|
||||||
/**
|
/**
|
||||||
* The filter is indecisive whether reflection access should be allowed.
|
* The filter is indecisive whether reflection access should be allowed. The next registered
|
||||||
* The next registered filter will be consulted to get the result. If
|
* filter will be consulted to get the result. If there is no next filter, this result acts like
|
||||||
* there is no next filter, this result acts like {@link #ALLOW}.
|
* {@link #ALLOW}.
|
||||||
*/
|
*/
|
||||||
INDECISIVE,
|
INDECISIVE,
|
||||||
/**
|
/**
|
||||||
* Blocks reflection access if a member of the class is not accessible
|
* Blocks reflection access if a member of the class is not accessible by default and would have
|
||||||
* by default and would have to be made accessible. This is unaffected
|
* to be made accessible. This is unaffected by any {@code java} command line arguments being
|
||||||
* by any {@code java} command line arguments being used to make packages
|
* used to make packages accessible, or by module declaration directives which <i>open</i> the
|
||||||
* accessible, or by module declaration directives which <i>open</i> the
|
* complete module or certain packages for reflection and will consider such packages
|
||||||
* complete module or certain packages for reflection and will consider
|
* inaccessible.
|
||||||
* such packages inaccessible.
|
|
||||||
*
|
*
|
||||||
* <p>Note that this <b>only works for Java 9 and higher</b>, for older
|
* <p>Note that this <b>only works for Java 9 and higher</b>, for older Java versions its
|
||||||
* Java versions its functionality will be limited and it might behave like
|
* functionality will be limited and it might behave like {@link #ALLOW}. Access checks are only
|
||||||
* {@link #ALLOW}. Access checks are only performed as defined by the Java
|
* performed as defined by the Java Language Specification (<a
|
||||||
* Language Specification (<a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.6">JLS 11 §6.6</a>),
|
* href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.6">JLS 11
|
||||||
* restrictions imposed by a {@link SecurityManager} are not considered.
|
* §6.6</a>), restrictions imposed by a {@link SecurityManager} are not considered.
|
||||||
*
|
*
|
||||||
* <p>This result type is mainly intended to help enforce the access checks of
|
* <p>This result type is mainly intended to help enforce the access checks of the Java Platform
|
||||||
* the Java Platform Module System. It allows detecting illegal access, even if
|
* Module System. It allows detecting illegal access, even if the used Java version would only
|
||||||
* the used Java version would only log a warning, or is configured to open
|
* log a warning, or is configured to open packages for reflection using command line arguments.
|
||||||
* packages for reflection using command line arguments.
|
|
||||||
*
|
*
|
||||||
* @see AccessibleObject#canAccess(Object)
|
* @see AccessibleObject#canAccess(Object)
|
||||||
*/
|
*/
|
||||||
BLOCK_INACCESSIBLE,
|
BLOCK_INACCESSIBLE,
|
||||||
/**
|
/**
|
||||||
* Blocks all reflection access for the class. Other means for serializing
|
* Blocks all reflection access for the class. Other means for serializing and deserializing the
|
||||||
* and deserializing the class, such as a {@link TypeAdapter}, have to
|
* class, such as a {@link TypeAdapter}, have to be used.
|
||||||
* be used.
|
|
||||||
*/
|
*/
|
||||||
BLOCK_ALL
|
BLOCK_ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks all reflection access to members of standard Java classes which are
|
* Blocks all reflection access to members of standard Java classes which are not accessible by
|
||||||
* not accessible by default. However, reflection access is still allowed for
|
* default. However, reflection access is still allowed for classes for which all fields are
|
||||||
* classes for which all fields are accessible and which have an accessible
|
* accessible and which have an accessible no-args constructor (or for which an {@link
|
||||||
* no-args constructor (or for which an {@link InstanceCreator} has been registered).
|
* InstanceCreator} has been registered).
|
||||||
*
|
*
|
||||||
* <p>If this filter encounters a class other than a standard Java class it
|
* <p>If this filter encounters a class other than a standard Java class it returns {@link
|
||||||
* returns {@link FilterResult#INDECISIVE}.
|
* FilterResult#INDECISIVE}.
|
||||||
*
|
*
|
||||||
* <p>This filter is mainly intended to help enforcing the access checks of
|
* <p>This filter is mainly intended to help enforcing the access checks of Java Platform Module
|
||||||
* Java Platform Module System. It allows detecting illegal access, even if
|
* System. It allows detecting illegal access, even if the used Java version would only log a
|
||||||
* the used Java version would only log a warning, or is configured to open
|
* warning, or is configured to open packages for reflection. However, this filter <b>only works
|
||||||
* packages for reflection. However, this filter <b>only works for Java 9 and
|
* for Java 9 and higher</b>, when using an older Java version its functionality will be limited.
|
||||||
* higher</b>, when using an older Java version its functionality will be
|
|
||||||
* limited.
|
|
||||||
*
|
*
|
||||||
* <p>Note that this filter might not cover all standard Java classes. Currently
|
* <p>Note that this filter might not cover all standard Java classes. Currently only classes in a
|
||||||
* only classes in a {@code java.*} or {@code javax.*} package are considered. The
|
* {@code java.*} or {@code javax.*} package are considered. The set of detected classes might be
|
||||||
* set of detected classes might be expanded in the future without prior notice.
|
* expanded in the future without prior notice.
|
||||||
*
|
*
|
||||||
* @see FilterResult#BLOCK_INACCESSIBLE
|
* @see FilterResult#BLOCK_INACCESSIBLE
|
||||||
*/
|
*/
|
||||||
ReflectionAccessFilter BLOCK_INACCESSIBLE_JAVA = new ReflectionAccessFilter() {
|
ReflectionAccessFilter BLOCK_INACCESSIBLE_JAVA =
|
||||||
@Override public FilterResult check(Class<?> rawClass) {
|
new ReflectionAccessFilter() {
|
||||||
return ReflectionAccessFilterHelper.isJavaType(rawClass)
|
@Override
|
||||||
? FilterResult.BLOCK_INACCESSIBLE
|
public FilterResult check(Class<?> rawClass) {
|
||||||
: FilterResult.INDECISIVE;
|
return ReflectionAccessFilterHelper.isJavaType(rawClass)
|
||||||
}
|
? FilterResult.BLOCK_INACCESSIBLE
|
||||||
};
|
: FilterResult.INDECISIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ReflectionAccessFilter#BLOCK_INACCESSIBLE_JAVA";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks all reflection access to members of standard Java classes.
|
* Blocks all reflection access to members of standard Java classes.
|
||||||
*
|
*
|
||||||
* <p>If this filter encounters a class other than a standard Java class it
|
* <p>If this filter encounters a class other than a standard Java class it returns {@link
|
||||||
* returns {@link FilterResult#INDECISIVE}.
|
* FilterResult#INDECISIVE}.
|
||||||
*
|
*
|
||||||
* <p>This filter is mainly intended to prevent depending on implementation
|
* <p>This filter is mainly intended to prevent depending on implementation details of the Java
|
||||||
* details of the Java platform and to help applications prepare for upgrading
|
* platform and to help applications prepare for upgrading to the Java Platform Module System.
|
||||||
* to the Java Platform Module System.
|
|
||||||
*
|
*
|
||||||
* <p>Note that this filter might not cover all standard Java classes. Currently
|
* <p>Note that this filter might not cover all standard Java classes. Currently only classes in a
|
||||||
* only classes in a {@code java.*} or {@code javax.*} package are considered. The
|
* {@code java.*} or {@code javax.*} package are considered. The set of detected classes might be
|
||||||
* set of detected classes might be expanded in the future without prior notice.
|
* expanded in the future without prior notice.
|
||||||
*
|
*
|
||||||
* @see #BLOCK_INACCESSIBLE_JAVA
|
* @see #BLOCK_INACCESSIBLE_JAVA
|
||||||
* @see FilterResult#BLOCK_ALL
|
* @see FilterResult#BLOCK_ALL
|
||||||
*/
|
*/
|
||||||
ReflectionAccessFilter BLOCK_ALL_JAVA = new ReflectionAccessFilter() {
|
ReflectionAccessFilter BLOCK_ALL_JAVA =
|
||||||
@Override public FilterResult check(Class<?> rawClass) {
|
new ReflectionAccessFilter() {
|
||||||
return ReflectionAccessFilterHelper.isJavaType(rawClass)
|
@Override
|
||||||
? FilterResult.BLOCK_ALL
|
public FilterResult check(Class<?> rawClass) {
|
||||||
: FilterResult.INDECISIVE;
|
return ReflectionAccessFilterHelper.isJavaType(rawClass)
|
||||||
}
|
? FilterResult.BLOCK_ALL
|
||||||
};
|
: FilterResult.INDECISIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ReflectionAccessFilter#BLOCK_ALL_JAVA";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks all reflection access to members of standard Android classes.
|
* Blocks all reflection access to members of standard Android classes.
|
||||||
*
|
*
|
||||||
* <p>If this filter encounters a class other than a standard Android class it
|
* <p>If this filter encounters a class other than a standard Android class it returns {@link
|
||||||
* returns {@link FilterResult#INDECISIVE}.
|
* FilterResult#INDECISIVE}.
|
||||||
*
|
*
|
||||||
* <p>This filter is mainly intended to prevent depending on implementation
|
* <p>This filter is mainly intended to prevent depending on implementation details of the Android
|
||||||
* details of the Android platform.
|
* platform.
|
||||||
*
|
*
|
||||||
* <p>Note that this filter might not cover all standard Android classes. Currently
|
* <p>Note that this filter might not cover all standard Android classes. Currently only classes
|
||||||
* only classes in an {@code android.*} or {@code androidx.*} package, and standard
|
* in an {@code android.*} or {@code androidx.*} package, and standard Java classes in a {@code
|
||||||
* Java classes in a {@code java.*} or {@code javax.*} package are considered. The
|
* java.*} or {@code javax.*} package are considered. The set of detected classes might be
|
||||||
* set of detected classes might be expanded in the future without prior notice.
|
* expanded in the future without prior notice.
|
||||||
*
|
*
|
||||||
* @see FilterResult#BLOCK_ALL
|
* @see FilterResult#BLOCK_ALL
|
||||||
*/
|
*/
|
||||||
ReflectionAccessFilter BLOCK_ALL_ANDROID = new ReflectionAccessFilter() {
|
ReflectionAccessFilter BLOCK_ALL_ANDROID =
|
||||||
@Override public FilterResult check(Class<?> rawClass) {
|
new ReflectionAccessFilter() {
|
||||||
return ReflectionAccessFilterHelper.isAndroidType(rawClass)
|
@Override
|
||||||
? FilterResult.BLOCK_ALL
|
public FilterResult check(Class<?> rawClass) {
|
||||||
: FilterResult.INDECISIVE;
|
return ReflectionAccessFilterHelper.isAndroidType(rawClass)
|
||||||
}
|
? FilterResult.BLOCK_ALL
|
||||||
};
|
: FilterResult.INDECISIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ReflectionAccessFilter#BLOCK_ALL_ANDROID";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks all reflection access to members of classes belonging to programming
|
* Blocks all reflection access to members of classes belonging to programming language platforms,
|
||||||
* language platforms, such as Java, Android, Kotlin or Scala.
|
* such as Java, Android, Kotlin or Scala.
|
||||||
*
|
*
|
||||||
* <p>If this filter encounters a class other than a standard platform class it
|
* <p>If this filter encounters a class other than a standard platform class it returns {@link
|
||||||
* returns {@link FilterResult#INDECISIVE}.
|
* FilterResult#INDECISIVE}.
|
||||||
*
|
*
|
||||||
* <p>This filter is mainly intended to prevent depending on implementation
|
* <p>This filter is mainly intended to prevent depending on implementation details of the
|
||||||
* details of the platform classes.
|
* platform classes.
|
||||||
*
|
*
|
||||||
* <p>Note that this filter might not cover all platform classes. Currently it
|
* <p>Note that this filter might not cover all platform classes. Currently it combines the
|
||||||
* combines the filters {@link #BLOCK_ALL_JAVA} and {@link #BLOCK_ALL_ANDROID},
|
* filters {@link #BLOCK_ALL_JAVA} and {@link #BLOCK_ALL_ANDROID}, and checks for other
|
||||||
* and checks for other language-specific platform classes like {@code kotlin.*}.
|
* language-specific platform classes like {@code kotlin.*}. The set of detected classes might be
|
||||||
* The set of detected classes might be expanded in the future without prior notice.
|
* expanded in the future without prior notice.
|
||||||
*
|
*
|
||||||
* @see FilterResult#BLOCK_ALL
|
* @see FilterResult#BLOCK_ALL
|
||||||
*/
|
*/
|
||||||
ReflectionAccessFilter BLOCK_ALL_PLATFORM = new ReflectionAccessFilter() {
|
ReflectionAccessFilter BLOCK_ALL_PLATFORM =
|
||||||
@Override public FilterResult check(Class<?> rawClass) {
|
new ReflectionAccessFilter() {
|
||||||
return ReflectionAccessFilterHelper.isAnyPlatformType(rawClass)
|
@Override
|
||||||
? FilterResult.BLOCK_ALL
|
public FilterResult check(Class<?> rawClass) {
|
||||||
: FilterResult.INDECISIVE;
|
return ReflectionAccessFilterHelper.isAnyPlatformType(rawClass)
|
||||||
}
|
? FilterResult.BLOCK_ALL
|
||||||
};
|
: FilterResult.INDECISIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ReflectionAccessFilter#BLOCK_ALL_PLATFORM";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if reflection access should be allowed for a class.
|
* Checks if reflection access should be allowed for a class.
|
||||||
*
|
*
|
||||||
* @param rawClass
|
* @param rawClass Class to check
|
||||||
* Class to check
|
* @return Result indicating whether reflection access is allowed
|
||||||
* @return
|
|
||||||
* Result indicating whether reflection access is allowed
|
|
||||||
*/
|
*/
|
||||||
FilterResult check(Class<?> rawClass);
|
FilterResult check(Class<?> rawClass);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,26 +4,25 @@ import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or
|
* Modes that indicate how strictly a JSON {@linkplain JsonReader reader} or {@linkplain JsonWriter
|
||||||
* {@linkplain JsonWriter writer} follows the syntax laid out in the
|
* writer} follows the syntax laid out in the <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC
|
||||||
* <a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259 JSON specification</a>.
|
* 8259 JSON specification</a>.
|
||||||
*
|
*
|
||||||
* <p>You can look at {@link JsonReader#setStrictness(Strictness)} to see how the strictness
|
* <p>You can look at {@link JsonReader#setStrictness(Strictness)} to see how the strictness affects
|
||||||
* affects the {@link JsonReader} and you can look at
|
* the {@link JsonReader} and you can look at {@link JsonWriter#setStrictness(Strictness)} to see
|
||||||
* {@link JsonWriter#setStrictness(Strictness)} to see how the strictness
|
* how the strictness affects the {@link JsonWriter}.
|
||||||
* affects the {@link JsonWriter}.</p>
|
|
||||||
*
|
*
|
||||||
* @see JsonReader#setStrictness(Strictness)
|
* @see JsonReader#setStrictness(Strictness)
|
||||||
* @see JsonWriter#setStrictness(Strictness)
|
* @see JsonWriter#setStrictness(Strictness)
|
||||||
* @since $next-version$
|
* @since $next-version$
|
||||||
*/
|
*/
|
||||||
public enum Strictness {
|
public enum Strictness {
|
||||||
/** Allow large deviations from the JSON specification. */
|
/** Allow large deviations from the JSON specification. */
|
||||||
LENIENT,
|
LENIENT,
|
||||||
|
|
||||||
/** Allow certain small deviations from the JSON specification for legacy reasons. */
|
/** Allow certain small deviations from the JSON specification for legacy reasons. */
|
||||||
LEGACY_STRICT,
|
LEGACY_STRICT,
|
||||||
|
|
||||||
/** Strict compliance with the JSON specification. */
|
/** Strict compliance with the JSON specification. */
|
||||||
STRICT
|
STRICT
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,16 @@
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
import com.google.gson.internal.LazilyParsedNumber;
|
import com.google.gson.internal.LazilyParsedNumber;
|
||||||
|
import com.google.gson.internal.NumberLimits;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.MalformedJsonException;
|
import com.google.gson.stream.MalformedJsonException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enumeration that defines two standard number reading strategies and a couple of
|
* An enumeration that defines two standard number reading strategies and a couple of strategies to
|
||||||
* strategies to overcome some historical Gson limitations while deserializing numbers as
|
* overcome some historical Gson limitations while deserializing numbers as {@link Object} and
|
||||||
* {@link Object} and {@link Number}.
|
* {@link Number}.
|
||||||
*
|
*
|
||||||
* @see ToNumberStrategy
|
* @see ToNumberStrategy
|
||||||
* @since 2.8.9
|
* @since 2.8.9
|
||||||
|
@ -33,37 +34,39 @@ import java.math.BigDecimal;
|
||||||
public enum ToNumberPolicy implements ToNumberStrategy {
|
public enum ToNumberPolicy implements ToNumberStrategy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this policy will ensure that numbers will be read as {@link Double} values.
|
* Using this policy will ensure that numbers will be read as {@link Double} values. This is the
|
||||||
* This is the default strategy used during deserialization of numbers as {@link Object}.
|
* default strategy used during deserialization of numbers as {@link Object}.
|
||||||
*/
|
*/
|
||||||
DOUBLE {
|
DOUBLE {
|
||||||
@Override public Double readNumber(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Double readNumber(JsonReader in) throws IOException {
|
||||||
return in.nextDouble();
|
return in.nextDouble();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this policy will ensure that numbers will be read as a lazily parsed number backed
|
* Using this policy will ensure that numbers will be read as a lazily parsed number backed by a
|
||||||
* by a string. This is the default strategy used during deserialization of numbers as
|
* string. This is the default strategy used during deserialization of numbers as {@link Number}.
|
||||||
* {@link Number}.
|
|
||||||
*/
|
*/
|
||||||
LAZILY_PARSED_NUMBER {
|
LAZILY_PARSED_NUMBER {
|
||||||
@Override public Number readNumber(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Number readNumber(JsonReader in) throws IOException {
|
||||||
return new LazilyParsedNumber(in.nextString());
|
return new LazilyParsedNumber(in.nextString());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this policy will ensure that numbers will be read as {@link Long} or {@link Double}
|
* Using this policy will ensure that numbers will be read as {@link Long} or {@link Double}
|
||||||
* values depending on how JSON numbers are represented: {@code Long} if the JSON number can
|
* values depending on how JSON numbers are represented: {@code Long} if the JSON number can be
|
||||||
* be parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a
|
* parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a {@code
|
||||||
* {@code Double} value. If the parsed double-precision number results in a positive or negative
|
* Double} value. If the parsed double-precision number results in a positive or negative infinity
|
||||||
* infinity ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the
|
* ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the {@code
|
||||||
* {@code JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException}
|
* JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} is
|
||||||
* is thrown.
|
* thrown.
|
||||||
*/
|
*/
|
||||||
LONG_OR_DOUBLE {
|
LONG_OR_DOUBLE {
|
||||||
@Override public Number readNumber(JsonReader in) throws IOException, JsonParseException {
|
@Override
|
||||||
|
public Number readNumber(JsonReader in) throws IOException, JsonParseException {
|
||||||
String value = in.nextString();
|
String value = in.nextString();
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(value);
|
return Long.parseLong(value);
|
||||||
|
@ -71,29 +74,32 @@ public enum ToNumberPolicy implements ToNumberStrategy {
|
||||||
try {
|
try {
|
||||||
Double d = Double.valueOf(value);
|
Double d = Double.valueOf(value);
|
||||||
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
|
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
|
||||||
throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
|
throw new MalformedJsonException(
|
||||||
|
"JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
|
||||||
}
|
}
|
||||||
return d;
|
return d;
|
||||||
} catch (NumberFormatException doubleE) {
|
} catch (NumberFormatException doubleE) {
|
||||||
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE);
|
throw new JsonParseException(
|
||||||
|
"Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using this policy will ensure that numbers will be read as numbers of arbitrary length
|
* Using this policy will ensure that numbers will be read as numbers of arbitrary length using
|
||||||
* using {@link BigDecimal}.
|
* {@link BigDecimal}.
|
||||||
*/
|
*/
|
||||||
BIG_DECIMAL {
|
BIG_DECIMAL {
|
||||||
@Override public BigDecimal readNumber(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public BigDecimal readNumber(JsonReader in) throws IOException {
|
||||||
String value = in.nextString();
|
String value = in.nextString();
|
||||||
try {
|
try {
|
||||||
return new BigDecimal(value);
|
return NumberLimits.parseBigDecimal(value);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
|
throw new JsonParseException(
|
||||||
|
"Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,20 +20,20 @@ import com.google.gson.stream.JsonReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A strategy that is used to control how numbers should be deserialized for {@link Object} and {@link Number}
|
* A strategy that is used to control how numbers should be deserialized for {@link Object} and
|
||||||
* when a concrete type of the deserialized number is unknown in advance. By default, Gson uses the following
|
* {@link Number} when a concrete type of the deserialized number is unknown in advance. By default,
|
||||||
* deserialization strategies:
|
* Gson uses the following deserialization strategies:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link Double} values are returned for JSON numbers if the deserialization type is declared as
|
* <li>{@link Double} values are returned for JSON numbers if the deserialization type is declared
|
||||||
* {@code Object}, see {@link ToNumberPolicy#DOUBLE};</li>
|
* as {@code Object}, see {@link ToNumberPolicy#DOUBLE};
|
||||||
* <li>Lazily parsed number values are returned if the deserialization type is declared as {@code Number},
|
* <li>Lazily parsed number values are returned if the deserialization type is declared as {@code
|
||||||
* see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.</li>
|
* Number}, see {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>For historical reasons, Gson does not support deserialization of arbitrary-length numbers for
|
* <p>For historical reasons, Gson does not support deserialization of arbitrary-length numbers for
|
||||||
* {@code Object} and {@code Number} by default, potentially causing precision loss. However,
|
* {@code Object} and {@code Number} by default, potentially causing precision loss. However, <a
|
||||||
* <a href="https://tools.ietf.org/html/rfc8259#section-6">RFC 8259</a> permits this:
|
* href="https://tools.ietf.org/html/rfc8259#section-6">RFC 8259</a> permits this:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* This specification allows implementations to set limits on the range
|
* This specification allows implementations to set limits on the range
|
||||||
|
@ -50,7 +50,7 @@ import java.io.IOException;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or
|
* <p>To overcome the precision loss, use for example {@link ToNumberPolicy#LONG_OR_DOUBLE} or
|
||||||
* {@link ToNumberPolicy#BIG_DECIMAL}.</p>
|
* {@link ToNumberPolicy#BIG_DECIMAL}.
|
||||||
*
|
*
|
||||||
* @see ToNumberPolicy
|
* @see ToNumberPolicy
|
||||||
* @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy)
|
* @see GsonBuilder#setObjectToNumberStrategy(ToNumberStrategy)
|
||||||
|
@ -60,8 +60,8 @@ import java.io.IOException;
|
||||||
public interface ToNumberStrategy {
|
public interface ToNumberStrategy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a number from the given JSON reader. A strategy is supposed to read a single value from the
|
* Reads a number from the given JSON reader. A strategy is supposed to read a single value from
|
||||||
* reader, and the read value is guaranteed never to be {@code null}.
|
* the reader, and the read value is guaranteed never to be {@code null}.
|
||||||
*
|
*
|
||||||
* @param in JSON reader to read a number from
|
* @param in JSON reader to read a number from
|
||||||
* @return number read from the JSON reader.
|
* @return number read from the JSON reader.
|
||||||
|
|
|
@ -31,66 +31,69 @@ import java.io.Writer;
|
||||||
* Converts Java objects to and from JSON.
|
* Converts Java objects to and from JSON.
|
||||||
*
|
*
|
||||||
* <h2>Defining a type's JSON form</h2>
|
* <h2>Defining a type's JSON form</h2>
|
||||||
* 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,
|
* By default Gson converts application classes to JSON using its built-in type adapters. If Gson's
|
||||||
* extend this class to customize the conversion. Here's an example of a type
|
* default JSON conversion isn't appropriate for a type, extend this class to customize the
|
||||||
* adapter for an (X,Y) coordinate point: <pre>{@code
|
* conversion. Here's an example of a type adapter for an (X,Y) coordinate point:
|
||||||
* public class PointAdapter extends TypeAdapter<Point> {
|
*
|
||||||
* public Point read(JsonReader reader) throws IOException {
|
* <pre>{@code
|
||||||
* if (reader.peek() == JsonToken.NULL) {
|
* public class PointAdapter extends TypeAdapter<Point> {
|
||||||
* reader.nextNull();
|
* public Point read(JsonReader reader) throws IOException {
|
||||||
* return null;
|
* if (reader.peek() == JsonToken.NULL) {
|
||||||
* }
|
* reader.nextNull();
|
||||||
* String xy = reader.nextString();
|
* return null;
|
||||||
* String[] parts = xy.split(",");
|
|
||||||
* int x = Integer.parseInt(parts[0]);
|
|
||||||
* int y = Integer.parseInt(parts[1]);
|
|
||||||
* return new Point(x, y);
|
|
||||||
* }
|
* }
|
||||||
* public void write(JsonWriter writer, Point value) throws IOException {
|
* String xy = reader.nextString();
|
||||||
* if (value == null) {
|
* String[] parts = xy.split(",");
|
||||||
* writer.nullValue();
|
* int x = Integer.parseInt(parts[0]);
|
||||||
* return;
|
* int y = Integer.parseInt(parts[1]);
|
||||||
* }
|
* return new Point(x, y);
|
||||||
* String xy = value.getX() + "," + value.getY();
|
* }
|
||||||
* writer.value(xy);
|
* public void write(JsonWriter writer, Point value) throws IOException {
|
||||||
|
* if (value == null) {
|
||||||
|
* writer.nullValue();
|
||||||
|
* return;
|
||||||
* }
|
* }
|
||||||
* }}</pre>
|
* String xy = value.getX() + "," + value.getY();
|
||||||
* With this type adapter installed, Gson will convert {@code Points} to JSON as
|
* writer.value(xy);
|
||||||
* strings like {@code "5,8"} rather than objects like {@code {"x":5,"y":8}}. In
|
* }
|
||||||
* this case the type adapter binds a rich Java class to a compact JSON value.
|
* }
|
||||||
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>The {@link #read(JsonReader) read()} method must read exactly one value
|
* With this type adapter installed, Gson will convert {@code Points} to JSON as strings like {@code
|
||||||
* and {@link #write(JsonWriter,Object) write()} must write exactly one value.
|
* "5,8"} rather than objects like {@code {"x":5,"y":8}}. In this case the type adapter binds a rich
|
||||||
* For primitive types this is means readers should make exactly one call to
|
* Java class to a compact JSON value.
|
||||||
* {@code nextBoolean()}, {@code nextDouble()}, {@code nextInt()}, {@code
|
|
||||||
* nextLong()}, {@code nextString()} or {@code nextNull()}. Writers should make
|
|
||||||
* exactly one call to one of <code>value()</code> or <code>nullValue()</code>.
|
|
||||||
* For arrays, type adapters should start with a call to {@code beginArray()},
|
|
||||||
* convert all elements, and finish with a call to {@code endArray()}. For
|
|
||||||
* objects, they should start with {@code beginObject()}, convert the object,
|
|
||||||
* and finish with {@code endObject()}. Failing to convert a value or converting
|
|
||||||
* too many values may cause the application to crash.
|
|
||||||
*
|
*
|
||||||
* <p>Type adapters should be prepared to read null from the stream and write it
|
* <p>The {@link #read(JsonReader) read()} method must read exactly one value and {@link
|
||||||
* to the stream. Alternatively, they should use {@link #nullSafe()} method while
|
* #write(JsonWriter,Object) write()} must write exactly one value. For primitive types this is
|
||||||
* registering the type adapter with Gson. If your {@code Gson} instance
|
* means readers should make exactly one call to {@code nextBoolean()}, {@code nextDouble()}, {@code
|
||||||
* has been configured to {@link GsonBuilder#serializeNulls()}, these nulls will be
|
* nextInt()}, {@code nextLong()}, {@code nextString()} or {@code nextNull()}. Writers should make
|
||||||
* written to the final document. Otherwise the value (and the corresponding name
|
* exactly one call to one of {@code value()} or {@code nullValue()}. For arrays, type adapters
|
||||||
* when writing to a JSON object) will be omitted automatically. In either case
|
* should start with a call to {@code beginArray()}, convert all elements, and finish with a call to
|
||||||
* your type adapter must handle null.
|
* {@code endArray()}. For objects, they should start with {@code beginObject()}, convert the
|
||||||
|
* object, and finish with {@code endObject()}. Failing to convert a value or converting too many
|
||||||
|
* values may cause the application to crash.
|
||||||
*
|
*
|
||||||
* <p>Type adapters should be stateless and thread-safe, otherwise the thread-safety
|
* <p>Type adapters should be prepared to read null from the stream and write it to the stream.
|
||||||
* guarantees of {@link Gson} might not apply.
|
* Alternatively, they should use {@link #nullSafe()} method while registering the type adapter with
|
||||||
|
* Gson. If your {@code Gson} instance has been configured to {@link GsonBuilder#serializeNulls()},
|
||||||
|
* these nulls will be written to the final document. Otherwise the value (and the corresponding
|
||||||
|
* name when writing to a JSON object) will be omitted automatically. In either case your type
|
||||||
|
* adapter must handle null.
|
||||||
*
|
*
|
||||||
* <p>To use a custom type adapter with Gson, you must <i>register</i> it with a
|
* <p>Type adapters should be stateless and thread-safe, otherwise the thread-safety guarantees of
|
||||||
* {@link GsonBuilder}: <pre>{@code
|
* {@link Gson} might not apply.
|
||||||
* GsonBuilder builder = new GsonBuilder();
|
*
|
||||||
* builder.registerTypeAdapter(Point.class, new PointAdapter());
|
* <p>To use a custom type adapter with Gson, you must <i>register</i> it with a {@link
|
||||||
* // if PointAdapter didn't check for nulls in its read/write methods, you should instead use
|
* GsonBuilder}:
|
||||||
* // builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe());
|
*
|
||||||
* ...
|
* <pre>{@code
|
||||||
* Gson gson = builder.create();
|
* 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
|
||||||
|
* // builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe());
|
||||||
|
* ...
|
||||||
|
* Gson gson = builder.create();
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
|
@ -117,12 +120,10 @@ import java.io.Writer;
|
||||||
//
|
//
|
||||||
public abstract class TypeAdapter<T> {
|
public abstract class TypeAdapter<T> {
|
||||||
|
|
||||||
public TypeAdapter() {
|
public TypeAdapter() {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes one JSON value (an array, object, string, number, boolean or null)
|
* Writes one JSON value (an array, object, string, number, boolean or null) for {@code value}.
|
||||||
* for {@code value}.
|
|
||||||
*
|
*
|
||||||
* @param value the Java object to write. May be null.
|
* @param value the Java object to write. May be null.
|
||||||
*/
|
*/
|
||||||
|
@ -131,9 +132,9 @@ public abstract class TypeAdapter<T> {
|
||||||
/**
|
/**
|
||||||
* Converts {@code value} to a JSON document and writes it to {@code out}.
|
* Converts {@code value} to a JSON document and writes it to {@code out}.
|
||||||
*
|
*
|
||||||
* <p>A {@link JsonWriter} with default configuration is used for writing the
|
* <p>A {@link JsonWriter} with default configuration is used for writing the JSON data. To
|
||||||
* JSON data. To customize this behavior, create a {@link JsonWriter}, configure
|
* customize this behavior, create a {@link JsonWriter}, configure it and then use {@link
|
||||||
* it and then use {@link #write(JsonWriter, Object)} instead.
|
* #write(JsonWriter, Object)} instead.
|
||||||
*
|
*
|
||||||
* @param value the Java object to convert. May be {@code null}.
|
* @param value the Java object to convert. May be {@code null}.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
|
@ -143,71 +144,15 @@ public abstract class TypeAdapter<T> {
|
||||||
write(writer, value);
|
write(writer, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
|
|
||||||
* new TypeAdapter<Foo>() {
|
|
||||||
* public Foo read(JsonReader in) throws IOException {
|
|
||||||
* if (in.peek() == JsonToken.NULL) {
|
|
||||||
* in.nextNull();
|
|
||||||
* return null;
|
|
||||||
* }
|
|
||||||
* // read a Foo from in and return it
|
|
||||||
* }
|
|
||||||
* public void write(JsonWriter out, Foo src) throws IOException {
|
|
||||||
* if (src == null) {
|
|
||||||
* out.nullValue();
|
|
||||||
* return;
|
|
||||||
* }
|
|
||||||
* // write src as JSON to out
|
|
||||||
* }
|
|
||||||
* }).create();
|
|
||||||
* }</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
|
|
||||||
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
|
|
||||||
* new TypeAdapter<Foo>() {
|
|
||||||
* public Foo read(JsonReader in) throws IOException {
|
|
||||||
* // read a Foo from in and return it
|
|
||||||
* }
|
|
||||||
* public void write(JsonWriter out, Foo src) throws IOException {
|
|
||||||
* // write src as JSON to out
|
|
||||||
* }
|
|
||||||
* }.nullSafe()).create();
|
|
||||||
* }</pre>
|
|
||||||
* Note that we didn't need to check for nulls in our type adapter after we used nullSafe.
|
|
||||||
*/
|
|
||||||
public final TypeAdapter<T> nullSafe() {
|
|
||||||
return new TypeAdapter<T>() {
|
|
||||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
|
||||||
if (value == null) {
|
|
||||||
out.nullValue();
|
|
||||||
} else {
|
|
||||||
TypeAdapter.this.write(out, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override public T read(JsonReader reader) throws IOException {
|
|
||||||
if (reader.peek() == JsonToken.NULL) {
|
|
||||||
reader.nextNull();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return TypeAdapter.this.read(reader);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts {@code value} to a JSON document.
|
* Converts {@code value} to a JSON document.
|
||||||
*
|
*
|
||||||
* <p>A {@link JsonWriter} with default configuration is used for writing the
|
* <p>A {@link JsonWriter} with default configuration is used for writing the JSON data. To
|
||||||
* JSON data. To customize this behavior, create a {@link JsonWriter}, configure
|
* customize this behavior, create a {@link JsonWriter}, configure it and then use {@link
|
||||||
* it and then use {@link #write(JsonWriter, Object)} instead.
|
* #write(JsonWriter, Object)} instead.
|
||||||
*
|
*
|
||||||
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
|
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter,
|
||||||
|
* Object)}
|
||||||
* @param value the Java object to convert. May be {@code null}.
|
* @param value the Java object to convert. May be {@code null}.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
|
@ -226,7 +171,8 @@ public abstract class TypeAdapter<T> {
|
||||||
*
|
*
|
||||||
* @param value the Java object to convert. May be {@code null}.
|
* @param value the Java object to convert. May be {@code null}.
|
||||||
* @return the converted JSON tree. May be {@link JsonNull}.
|
* @return the converted JSON tree. May be {@link JsonNull}.
|
||||||
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
|
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter,
|
||||||
|
* Object)}
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
public final JsonElement toJsonTree(T value) {
|
public final JsonElement toJsonTree(T value) {
|
||||||
|
@ -240,8 +186,8 @@ public abstract class TypeAdapter<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads one JSON value (an array, object, string, number, boolean or null)
|
* Reads one JSON value (an array, object, string, number, boolean or null) and converts it to a
|
||||||
* and converts it to a Java object. Returns the converted object.
|
* Java object. Returns the converted object.
|
||||||
*
|
*
|
||||||
* @return the converted Java object. May be {@code null}.
|
* @return the converted Java object. May be {@code null}.
|
||||||
*/
|
*/
|
||||||
|
@ -250,13 +196,13 @@ public abstract class TypeAdapter<T> {
|
||||||
/**
|
/**
|
||||||
* Converts the JSON document in {@code in} to a Java object.
|
* Converts the JSON document in {@code in} to a Java object.
|
||||||
*
|
*
|
||||||
* <p>A {@link JsonReader} with default configuration (that is with
|
* <p>A {@link JsonReader} with default configuration (that is with {@link
|
||||||
* {@link Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data.
|
* Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data. To customize this
|
||||||
* To customize this behavior, create a {@link JsonReader}, configure it and then
|
* behavior, create a {@link JsonReader}, configure it and then use {@link #read(JsonReader)}
|
||||||
* use {@link #read(JsonReader)} instead.
|
* instead.
|
||||||
*
|
*
|
||||||
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
|
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements, or if there is
|
||||||
* or if there is trailing data.
|
* trailing data.
|
||||||
*
|
*
|
||||||
* @return the converted Java object. May be {@code null}.
|
* @return the converted Java object. May be {@code null}.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
|
@ -269,13 +215,13 @@ public abstract class TypeAdapter<T> {
|
||||||
/**
|
/**
|
||||||
* Converts the JSON document in {@code json} to a Java object.
|
* Converts the JSON document in {@code json} to a Java object.
|
||||||
*
|
*
|
||||||
* <p>A {@link JsonReader} with default configuration (that is with
|
* <p>A {@link JsonReader} with default configuration (that is with {@link
|
||||||
* {@link Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data.
|
* Strictness#LEGACY_STRICT} as strictness) is used for reading the JSON data. To customize this
|
||||||
* To customize this behavior, create a {@link JsonReader}, configure it and then
|
* behavior, create a {@link JsonReader}, configure it and then use {@link #read(JsonReader)}
|
||||||
* use {@link #read(JsonReader)} instead.
|
* instead.
|
||||||
*
|
*
|
||||||
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
|
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements, or if there is
|
||||||
* or if there is trailing data.
|
* trailing data.
|
||||||
*
|
*
|
||||||
* @return the converted Java object. May be {@code null}.
|
* @return the converted Java object. May be {@code null}.
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
|
@ -300,4 +246,67 @@ public abstract class TypeAdapter<T> {
|
||||||
throw new JsonIOException(e);
|
throw new JsonIOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
|
||||||
|
* new TypeAdapter<Foo>() {
|
||||||
|
* public Foo read(JsonReader in) throws IOException {
|
||||||
|
* if (in.peek() == JsonToken.NULL) {
|
||||||
|
* in.nextNull();
|
||||||
|
* return null;
|
||||||
|
* }
|
||||||
|
* // read a Foo from in and return it
|
||||||
|
* }
|
||||||
|
* public void write(JsonWriter out, Foo src) throws IOException {
|
||||||
|
* if (src == null) {
|
||||||
|
* out.nullValue();
|
||||||
|
* return;
|
||||||
|
* }
|
||||||
|
* // write src as JSON to out
|
||||||
|
* }
|
||||||
|
* }).create();
|
||||||
|
* }</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
|
||||||
|
* Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
|
||||||
|
* new TypeAdapter<Foo>() {
|
||||||
|
* public Foo read(JsonReader in) throws IOException {
|
||||||
|
* // read a Foo from in and return it
|
||||||
|
* }
|
||||||
|
* public void write(JsonWriter out, Foo src) throws IOException {
|
||||||
|
* // write src as JSON to out
|
||||||
|
* }
|
||||||
|
* }.nullSafe()).create();
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* Note that we didn't need to check for nulls in our type adapter after we used nullSafe.
|
||||||
|
*/
|
||||||
|
public final TypeAdapter<T> nullSafe() {
|
||||||
|
return new TypeAdapter<T>() {
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
out.nullValue();
|
||||||
|
} else {
|
||||||
|
TypeAdapter.this.write(out, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(JsonReader reader) throws IOException {
|
||||||
|
if (reader.peek() == JsonToken.NULL) {
|
||||||
|
reader.nextNull();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return TypeAdapter.this.read(reader);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,153 +19,152 @@ package com.google.gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates type adapters for set of related types. Type adapter factories are
|
* Creates type adapters for set of related types. Type adapter factories are most useful when
|
||||||
* most useful when several types share similar structure in their JSON form.
|
* several types share similar structure in their JSON form.
|
||||||
*
|
*
|
||||||
* <h2>Examples</h2>
|
* <h2>Examples</h2>
|
||||||
|
*
|
||||||
* <h3>Example: Converting enums to lowercase</h3>
|
* <h3>Example: Converting enums to lowercase</h3>
|
||||||
* In this example, we implement a factory that creates type adapters for all
|
|
||||||
* enums. The type adapters will write enums in lowercase, despite the fact
|
|
||||||
* that they're defined in {@code CONSTANT_CASE} in the corresponding Java
|
|
||||||
* model: <pre> {@code
|
|
||||||
*
|
*
|
||||||
* public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory {
|
* In this example, we implement a factory that creates type adapters for all enums. The type
|
||||||
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
* adapters will write enums in lowercase, despite the fact that they're defined in {@code
|
||||||
* Class<T> rawType = (Class<T>) type.getRawType();
|
* CONSTANT_CASE} in the corresponding Java model:
|
||||||
* if (!rawType.isEnum()) {
|
|
||||||
* return null;
|
|
||||||
* }
|
|
||||||
*
|
*
|
||||||
* final Map<String, T> lowercaseToConstant = new HashMap<>();
|
* <pre>{@code
|
||||||
* for (T constant : rawType.getEnumConstants()) {
|
* public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
* lowercaseToConstant.put(toLowercase(constant), constant);
|
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
* }
|
* Class<T> rawType = (Class<T>) type.getRawType();
|
||||||
*
|
* if (!rawType.isEnum()) {
|
||||||
* return new TypeAdapter<T>() {
|
* return null;
|
||||||
* public void write(JsonWriter out, T value) throws IOException {
|
|
||||||
* if (value == null) {
|
|
||||||
* out.nullValue();
|
|
||||||
* } else {
|
|
||||||
* out.value(toLowercase(value));
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public T read(JsonReader reader) throws IOException {
|
|
||||||
* if (reader.peek() == JsonToken.NULL) {
|
|
||||||
* reader.nextNull();
|
|
||||||
* return null;
|
|
||||||
* } else {
|
|
||||||
* return lowercaseToConstant.get(reader.nextString());
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* };
|
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* private String toLowercase(Object o) {
|
* final Map<String, T> lowercaseToConstant = new HashMap<>();
|
||||||
* return o.toString().toLowerCase(Locale.US);
|
* for (T constant : rawType.getEnumConstants()) {
|
||||||
|
* lowercaseToConstant.put(toLowercase(constant), constant);
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* return new TypeAdapter<T>() {
|
||||||
|
* public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
* if (value == null) {
|
||||||
|
* out.nullValue();
|
||||||
|
* } else {
|
||||||
|
* out.value(toLowercase(value));
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public T read(JsonReader reader) throws IOException {
|
||||||
|
* if (reader.peek() == JsonToken.NULL) {
|
||||||
|
* reader.nextNull();
|
||||||
|
* return null;
|
||||||
|
* } else {
|
||||||
|
* return lowercaseToConstant.get(reader.nextString());
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* };
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* private String toLowercase(Object o) {
|
||||||
|
* return o.toString().toLowerCase(Locale.US);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>Type adapter factories select which types they provide type adapters
|
* <p>Type adapter factories select which types they provide type adapters for. If a factory cannot
|
||||||
* for. If a factory cannot support a given type, it must return null when
|
* support a given type, it must return null when that type is passed to {@link #create}. Factories
|
||||||
* that type is passed to {@link #create}. Factories should expect {@code
|
* should expect {@code create()} to be called on them for many types and should return null for
|
||||||
* create()} to be called on them for many types and should return null for
|
* most of those types. In the above example the factory returns null for calls to {@code create()}
|
||||||
* most of those types. In the above example the factory returns null for
|
* where {@code type} is not an enum.
|
||||||
* calls to {@code create()} where {@code type} is not an enum.
|
|
||||||
*
|
*
|
||||||
* <p>A factory is typically called once per type, but the returned type
|
* <p>A factory is typically called once per type, but the returned type adapter may be used many
|
||||||
* adapter may be used many times. It is most efficient to do expensive work
|
* times. It is most efficient to do expensive work like reflection in {@code create()} so that the
|
||||||
* like reflection in {@code create()} so that the type adapter's {@code
|
* type adapter's {@code read()} and {@code write()} methods can be very fast. In this example the
|
||||||
* read()} and {@code write()} methods can be very fast. In this example the
|
|
||||||
* mapping from lowercase name to enum value is computed eagerly.
|
* mapping from lowercase name to enum value is computed eagerly.
|
||||||
*
|
*
|
||||||
* <p>As with type adapters, factories must be <i>registered</i> with a {@link
|
* <p>As with type adapters, factories must be <i>registered</i> with a {@link
|
||||||
* com.google.gson.GsonBuilder} for them to take effect: <pre> {@code
|
* com.google.gson.GsonBuilder} for them to take effect:
|
||||||
*
|
*
|
||||||
* GsonBuilder builder = new GsonBuilder();
|
* <pre>{@code
|
||||||
* builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
|
* GsonBuilder builder = new GsonBuilder();
|
||||||
* ...
|
* builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
|
||||||
* Gson gson = builder.create();
|
* ...
|
||||||
|
* Gson gson = builder.create();
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* If multiple factories support the same type, the factory registered earlier
|
*
|
||||||
* takes precedence.
|
* If multiple factories support the same type, the factory registered earlier takes precedence.
|
||||||
*
|
*
|
||||||
* <h3>Example: Composing other type adapters</h3>
|
* <h3>Example: Composing other type adapters</h3>
|
||||||
* In this example we implement a factory for Guava's {@code Multiset}
|
|
||||||
* collection type. The factory can be used to create type adapters for
|
|
||||||
* multisets of any element type: the type adapter for {@code
|
|
||||||
* Multiset<String>} is different from the type adapter for {@code
|
|
||||||
* Multiset<URL>}.
|
|
||||||
*
|
*
|
||||||
* <p>The type adapter <i>delegates</i> to another type adapter for the
|
* In this example we implement a factory for Guava's {@code Multiset} collection type. The factory
|
||||||
* multiset elements. It figures out the element type by reflecting on the
|
* can be used to create type adapters for multisets of any element type: the type adapter for
|
||||||
* multiset's type token. A {@code Gson} is passed in to {@code create} for
|
* {@code Multiset<String>} is different from the type adapter for {@code Multiset<URL>}.
|
||||||
* just this purpose: <pre> {@code
|
|
||||||
*
|
*
|
||||||
* public class MultisetTypeAdapterFactory implements TypeAdapterFactory {
|
* <p>The type adapter <i>delegates</i> to another type adapter for the multiset elements. It
|
||||||
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
* figures out the element type by reflecting on the multiset's type token. A {@code Gson} is passed
|
||||||
* Type type = typeToken.getType();
|
* in to {@code create} for just this purpose:
|
||||||
* if (typeToken.getRawType() != Multiset.class
|
*
|
||||||
* || !(type instanceof ParameterizedType)) {
|
* <pre>{@code
|
||||||
* return null;
|
* public class MultisetTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
* public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
|
* Type type = typeToken.getType();
|
||||||
|
* if (typeToken.getRawType() != Multiset.class
|
||||||
|
* || !(type instanceof ParameterizedType)) {
|
||||||
|
* return null;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||||
|
* TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
|
||||||
|
* return (TypeAdapter<T>) newMultisetAdapter(elementAdapter);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* private <E> TypeAdapter<Multiset<E>> newMultisetAdapter(
|
||||||
|
* final TypeAdapter<E> elementAdapter) {
|
||||||
|
* return new TypeAdapter<Multiset<E>>() {
|
||||||
|
* public void write(JsonWriter out, Multiset<E> value) throws IOException {
|
||||||
|
* if (value == null) {
|
||||||
|
* out.nullValue();
|
||||||
|
* return;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* out.beginArray();
|
||||||
|
* for (Multiset.Entry<E> entry : value.entrySet()) {
|
||||||
|
* out.value(entry.getCount());
|
||||||
|
* elementAdapter.write(out, entry.getElement());
|
||||||
|
* }
|
||||||
|
* out.endArray();
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
* public Multiset<E> read(JsonReader in) throws IOException {
|
||||||
* TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
|
* if (in.peek() == JsonToken.NULL) {
|
||||||
* return (TypeAdapter<T>) newMultisetAdapter(elementAdapter);
|
* in.nextNull();
|
||||||
* }
|
* return null;
|
||||||
*
|
|
||||||
* private <E> TypeAdapter<Multiset<E>> newMultisetAdapter(
|
|
||||||
* final TypeAdapter<E> elementAdapter) {
|
|
||||||
* return new TypeAdapter<Multiset<E>>() {
|
|
||||||
* public void write(JsonWriter out, Multiset<E> value) throws IOException {
|
|
||||||
* if (value == null) {
|
|
||||||
* out.nullValue();
|
|
||||||
* return;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* out.beginArray();
|
|
||||||
* for (Multiset.Entry<E> entry : value.entrySet()) {
|
|
||||||
* out.value(entry.getCount());
|
|
||||||
* elementAdapter.write(out, entry.getElement());
|
|
||||||
* }
|
|
||||||
* out.endArray();
|
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* public Multiset<E> read(JsonReader in) throws IOException {
|
* Multiset<E> result = LinkedHashMultiset.create();
|
||||||
* if (in.peek() == JsonToken.NULL) {
|
* in.beginArray();
|
||||||
* in.nextNull();
|
* while (in.hasNext()) {
|
||||||
* return null;
|
* int count = in.nextInt();
|
||||||
* }
|
* E element = elementAdapter.read(in);
|
||||||
*
|
* result.add(element, count);
|
||||||
* Multiset<E> result = LinkedHashMultiset.create();
|
|
||||||
* in.beginArray();
|
|
||||||
* while (in.hasNext()) {
|
|
||||||
* int count = in.nextInt();
|
|
||||||
* E element = elementAdapter.read(in);
|
|
||||||
* result.add(element, count);
|
|
||||||
* }
|
|
||||||
* in.endArray();
|
|
||||||
* return result;
|
|
||||||
* }
|
* }
|
||||||
* };
|
* in.endArray();
|
||||||
* }
|
* return result;
|
||||||
|
* }
|
||||||
|
* };
|
||||||
* }
|
* }
|
||||||
|
* }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* Delegating from one type adapter to another is extremely powerful; it's
|
*
|
||||||
* the foundation of how Gson converts Java objects and collections. Whenever
|
* Delegating from one type adapter to another is extremely powerful; it's the foundation of how
|
||||||
* possible your factory should retrieve its delegate type adapter in the
|
* Gson converts Java objects and collections. Whenever possible your factory should retrieve its
|
||||||
* {@code create()} method; this ensures potentially-expensive type adapter
|
* delegate type adapter in the {@code create()} method; this ensures potentially-expensive type
|
||||||
* creation happens only once.
|
* adapter creation happens only once.
|
||||||
*
|
*
|
||||||
* @since 2.1
|
* @since 2.1
|
||||||
*/
|
*/
|
||||||
public interface TypeAdapterFactory {
|
public interface TypeAdapterFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a type adapter for {@code type}, or null if this factory doesn't
|
* Returns a type adapter for {@code type}, or null if this factory doesn't support {@code type}.
|
||||||
* support {@code type}.
|
|
||||||
*/
|
*/
|
||||||
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
|
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,15 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation that indicates this member should be exposed for JSON
|
* An annotation that indicates this member should be exposed for JSON serialization or
|
||||||
* serialization or deserialization.
|
* deserialization.
|
||||||
*
|
*
|
||||||
* <p>This annotation has no effect unless you build {@link com.google.gson.Gson}
|
* <p>This annotation has no effect unless you build {@link com.google.gson.Gson} with a {@link
|
||||||
* with a {@link com.google.gson.GsonBuilder} and invoke
|
* com.google.gson.GsonBuilder} and invoke {@link
|
||||||
* {@link com.google.gson.GsonBuilder#excludeFieldsWithoutExposeAnnotation()}
|
* com.google.gson.GsonBuilder#excludeFieldsWithoutExposeAnnotation()} method.
|
||||||
* method.</p>
|
|
||||||
*
|
*
|
||||||
* <p>Here is an example of how this annotation is meant to be used:
|
* <p>Here is an example of how this annotation is meant to be used:
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class User {
|
* public class User {
|
||||||
* @Expose private String firstName;
|
* @Expose private String firstName;
|
||||||
|
@ -40,20 +40,21 @@ import java.lang.annotation.Target;
|
||||||
* private String password;
|
* private String password;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
* If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
|
|
||||||
* methods will use the {@code password} field along-with {@code firstName}, {@code lastName},
|
|
||||||
* and {@code emailAddress} for serialization and deserialization. However, if you created Gson
|
|
||||||
* with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()}
|
|
||||||
* then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the
|
|
||||||
* {@code password} field. This is because the {@code password} field is not marked with the
|
|
||||||
* {@code @Expose} annotation. Gson will also exclude {@code lastName} and {@code emailAddress}
|
|
||||||
* from serialization since {@code serialize} is set to {@code false}. Similarly, Gson will
|
|
||||||
* exclude {@code emailAddress} from deserialization since {@code deserialize} is set to false.
|
|
||||||
*
|
*
|
||||||
* <p>Note that another way to achieve the same effect would have been to just mark the
|
* If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()} methods
|
||||||
* {@code password} field as {@code transient}, and Gson would have excluded it even with default
|
* will use the {@code password} field along-with {@code firstName}, {@code lastName}, and {@code
|
||||||
* settings. The {@code @Expose} annotation is useful in a style of programming where you want to
|
* emailAddress} for serialization and deserialization. However, if you created Gson with {@code
|
||||||
* explicitly specify all fields that should get considered for serialization or deserialization.
|
* Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()} then the {@code
|
||||||
|
* toJson()} and {@code fromJson()} methods of Gson will exclude the {@code password} field. This is
|
||||||
|
* because the {@code password} field is not marked with the {@code @Expose} annotation. Gson will
|
||||||
|
* also exclude {@code lastName} and {@code emailAddress} from serialization since {@code serialize}
|
||||||
|
* is set to {@code false}. Similarly, Gson will exclude {@code emailAddress} from deserialization
|
||||||
|
* since {@code deserialize} is set to false.
|
||||||
|
*
|
||||||
|
* <p>Note that another way to achieve the same effect would have been to just mark the {@code
|
||||||
|
* password} field as {@code transient}, and Gson would have excluded it even with default settings.
|
||||||
|
* The {@code @Expose} annotation is useful in a style of programming where you want to explicitly
|
||||||
|
* specify all fields that should get considered for serialization or deserialization.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -62,19 +63,21 @@ import java.lang.annotation.Target;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.FIELD)
|
@Target(ElementType.FIELD)
|
||||||
public @interface Expose {
|
public @interface Expose {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If {@code true}, the field marked with this annotation is written out in the JSON while
|
* If {@code true}, the field marked with this annotation is written out in the JSON while
|
||||||
* serializing. If {@code false}, the field marked with this annotation is skipped from the
|
* serializing. If {@code false}, the field marked with this annotation is skipped from the
|
||||||
* serialized output. Defaults to {@code true}.
|
* serialized output. Defaults to {@code true}.
|
||||||
|
*
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
public boolean serialize() default true;
|
public boolean serialize() default true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If {@code true}, the field marked with this annotation is deserialized from the JSON.
|
* If {@code true}, the field marked with this annotation is deserialized from the JSON. If {@code
|
||||||
* If {@code false}, the field marked with this annotation is skipped during deserialization.
|
* false}, the field marked with this annotation is skipped during deserialization. Defaults to
|
||||||
* Defaults to {@code true}.
|
* {@code true}.
|
||||||
|
*
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
public boolean deserialize() default true;
|
public boolean deserialize() default true;
|
||||||
|
|
|
@ -29,10 +29,10 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class
|
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class or field.
|
||||||
* or field.
|
*
|
||||||
|
* <p>Here is an example of how this annotation is used:
|
||||||
*
|
*
|
||||||
* <p>Here is an example of how this annotation is used:</p>
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* @JsonAdapter(UserJsonAdapter.class)
|
* @JsonAdapter(UserJsonAdapter.class)
|
||||||
* public class User {
|
* public class User {
|
||||||
|
@ -68,6 +68,7 @@ import java.lang.annotation.Target;
|
||||||
* annotation, it will automatically be invoked to serialize/deserialize {@code User} instances.
|
* 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>
|
* <pre>
|
||||||
* private static final class Gadget {
|
* private static final class Gadget {
|
||||||
* @JsonAdapter(UserJsonAdapter.class)
|
* @JsonAdapter(UserJsonAdapter.class)
|
||||||
|
@ -79,39 +80,34 @@ import java.lang.annotation.Target;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* It's possible to specify different type adapters on a field, that
|
* It's possible to specify different type adapters on a field, that field's type, and in the {@link
|
||||||
* field's type, and in the {@link GsonBuilder}. Field annotations
|
* GsonBuilder}. Field annotations take precedence over {@code GsonBuilder}-registered type
|
||||||
* take precedence over {@code GsonBuilder}-registered type
|
|
||||||
* adapters, which in turn take precedence over annotated types.
|
* adapters, which in turn take precedence over annotated types.
|
||||||
*
|
*
|
||||||
* <p>The class referenced by this annotation must be either a {@link
|
* <p>The class referenced by this annotation must be either a {@link TypeAdapter} or a {@link
|
||||||
* TypeAdapter} or a {@link TypeAdapterFactory}, or must implement one
|
* TypeAdapterFactory}, or must implement one or both of {@link JsonDeserializer} or {@link
|
||||||
* or both of {@link JsonDeserializer} or {@link JsonSerializer}.
|
* JsonSerializer}. Using {@link TypeAdapterFactory} makes it possible to delegate to the enclosing
|
||||||
* Using {@link TypeAdapterFactory} makes it possible to delegate
|
* {@link Gson} instance. By default the specified adapter will not be called for {@code null}
|
||||||
* to the enclosing {@link Gson} instance. By default the specified
|
* values; set {@link #nullSafe()} to {@code false} to let the adapter handle {@code null} values
|
||||||
* adapter will not be called for {@code null} values; set {@link #nullSafe()}
|
* itself.
|
||||||
* 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:
|
||||||
*
|
*
|
||||||
* <p>The type adapter is created in the same way Gson creates instances of
|
|
||||||
* custom classes during deserialization, that means:
|
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>If a custom {@link InstanceCreator} has been registered for the
|
* <li>If a custom {@link InstanceCreator} has been registered for the adapter class, it will be
|
||||||
* adapter class, it will be used to create the instance
|
* used to create the instance
|
||||||
* <li>Otherwise, if the adapter class has a no-args constructor
|
* <li>Otherwise, if the adapter class has a no-args constructor (regardless of which visibility),
|
||||||
* (regardless of which visibility), it will be invoked to create
|
* it will be invoked to create the instance
|
||||||
* the instance
|
* <li>Otherwise, JDK {@code Unsafe} will be used to create the instance; see {@link
|
||||||
* <li>Otherwise, JDK {@code Unsafe} will be used to create the instance;
|
* GsonBuilder#disableJdkUnsafe()} for the unexpected side-effects this might have
|
||||||
* see {@link GsonBuilder#disableJdkUnsafe()} for the unexpected
|
|
||||||
* side-effects this might have
|
|
||||||
* </ol>
|
* </ol>
|
||||||
*
|
*
|
||||||
* <p>{@code Gson} instances might cache the adapter they create for
|
* <p>{@code Gson} instances might cache the adapter they create for a {@code @JsonAdapter}
|
||||||
* a {@code @JsonAdapter} annotation. It is not guaranteed that a new
|
* annotation. It is not guaranteed that a new adapter is created every time the annotated class or
|
||||||
* adapter is created every time the annotated class or field is serialized
|
* field is serialized or deserialized.
|
||||||
* or deserialized.
|
|
||||||
*
|
*
|
||||||
* @since 2.3
|
* @since 2.3
|
||||||
*
|
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
* @author Jesse Wilson
|
* @author Jesse Wilson
|
||||||
|
@ -121,16 +117,19 @@ import java.lang.annotation.Target;
|
||||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||||
public @interface JsonAdapter {
|
public @interface JsonAdapter {
|
||||||
|
|
||||||
/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link JsonDeserializer} or {@link JsonSerializer}. */
|
/**
|
||||||
|
* Either a {@link TypeAdapter} or {@link TypeAdapterFactory}, or one or both of {@link
|
||||||
|
* JsonDeserializer} or {@link JsonSerializer}.
|
||||||
|
*/
|
||||||
Class<?> value();
|
Class<?> value();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the adapter referenced by {@link #value()} should be made {@linkplain TypeAdapter#nullSafe() null-safe}.
|
* 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
|
* <p>If {@code true} (the default), it will be made null-safe and Gson will handle {@code null}
|
||||||
* on serialization and JSON {@code null} on deserialization without calling the adapter. If {@code false},
|
* Java objects on serialization and JSON {@code null} on deserialization without calling the
|
||||||
* the adapter will have to handle the {@code null} values.
|
* adapter. If {@code false}, the adapter will have to handle the {@code null} values.
|
||||||
*/
|
*/
|
||||||
boolean nullSafe() default true;
|
boolean nullSafe() default true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,17 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation that indicates this member should be serialized to JSON with
|
* An annotation that indicates this member should be serialized to JSON with the provided name
|
||||||
* the provided name value as its field name.
|
* value as its field name.
|
||||||
*
|
*
|
||||||
* <p>This annotation will override any {@link com.google.gson.FieldNamingPolicy}, including
|
* <p>This annotation will override any {@link com.google.gson.FieldNamingPolicy}, including the
|
||||||
* the default field naming policy, that may have been set on the {@link com.google.gson.Gson}
|
* default field naming policy, that may have been set on the {@link com.google.gson.Gson} instance.
|
||||||
* instance. A different naming policy can set using the {@code GsonBuilder} class. See
|
* A different naming policy can set using the {@code GsonBuilder} class. See {@link
|
||||||
* {@link com.google.gson.GsonBuilder#setFieldNamingPolicy(com.google.gson.FieldNamingPolicy)}
|
* com.google.gson.GsonBuilder#setFieldNamingPolicy(com.google.gson.FieldNamingPolicy)} for more
|
||||||
* for more information.</p>
|
* information.
|
||||||
|
*
|
||||||
|
* <p>Here is an example of how this annotation is meant to be used:
|
||||||
*
|
*
|
||||||
* <p>Here is an example of how this annotation is meant to be used:</p>
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class MyClass {
|
* public class MyClass {
|
||||||
* @SerializedName("name") String a;
|
* @SerializedName("name") String a;
|
||||||
|
@ -47,8 +48,9 @@ import java.lang.annotation.Target;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>The following shows the output that is generated when serializing an instance of the
|
* <p>The following shows the output that is generated when serializing an instance of the above
|
||||||
* above example class:</p>
|
* example class:
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* MyClass target = new MyClass("v1", "v2", "v3");
|
* MyClass target = new MyClass("v1", "v2", "v3");
|
||||||
* Gson gson = new Gson();
|
* Gson gson = new Gson();
|
||||||
|
@ -59,9 +61,10 @@ import java.lang.annotation.Target;
|
||||||
* {"name":"v1","name1":"v2","c":"v3"}
|
* {"name":"v1","name1":"v2","c":"v3"}
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>NOTE: The value you specify in this annotation must be a valid JSON field name.</p>
|
* <p>NOTE: The value you specify in this annotation must be a valid JSON field name. While
|
||||||
* While deserializing, all values specified in the annotation will be deserialized into the field.
|
* deserializing, all values specified in the annotation will be deserialized into the field. For
|
||||||
* For example:
|
* example:
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class);
|
* MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class);
|
||||||
* assertEquals("v1", target.b);
|
* assertEquals("v1", target.b);
|
||||||
|
@ -70,10 +73,10 @@ import java.lang.annotation.Target;
|
||||||
* target = gson.fromJson("{'name3':'v3'}", MyClass.class);
|
* target = gson.fromJson("{'name3':'v3'}", MyClass.class);
|
||||||
* assertEquals("v3", target.b);
|
* assertEquals("v3", target.b);
|
||||||
* </pre>
|
* </pre>
|
||||||
|
*
|
||||||
* Note that MyClass.b is now deserialized from either name1, name2 or name3.
|
* Note that MyClass.b is now deserialized from either name1, name2 or name3.
|
||||||
*
|
*
|
||||||
* @see com.google.gson.FieldNamingPolicy
|
* @see com.google.gson.FieldNamingPolicy
|
||||||
*
|
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
|
@ -88,6 +91,7 @@ public @interface SerializedName {
|
||||||
* @return the desired name of the field when it is serialized or deserialized
|
* @return the desired name of the field when it is serialized or deserialized
|
||||||
*/
|
*/
|
||||||
String value();
|
String value();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The alternative names of the field when it is deserialized
|
* The alternative names of the field when it is deserialized
|
||||||
*
|
*
|
||||||
|
|
|
@ -24,14 +24,14 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation that indicates the version number since a member or a type has been present.
|
* An annotation that indicates the version number since a member or a type has been present. This
|
||||||
* This annotation is useful to manage versioning of your JSON classes for a web-service.
|
* annotation is useful to manage versioning of your JSON classes for a web-service.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>This annotation has no effect unless you build {@link com.google.gson.Gson} with a {@code
|
||||||
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
|
* GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
|
||||||
* {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
|
*
|
||||||
|
* <p>Here is an example of how this annotation is meant to be used:
|
||||||
*
|
*
|
||||||
* <p>Here is an example of how this annotation is meant to be used:</p>
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class User {
|
* public class User {
|
||||||
* private String firstName;
|
* private String firstName;
|
||||||
|
@ -44,9 +44,9 @@ import java.lang.annotation.Target;
|
||||||
*
|
*
|
||||||
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
|
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
|
||||||
* methods will use all the fields for serialization and deserialization. However, if you created
|
* methods will use all the fields for serialization and deserialization. However, if you created
|
||||||
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the
|
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the {@code
|
||||||
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field
|
* toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field since
|
||||||
* since it's version number is set to {@code 1.1}.</p>
|
* it's version number is set to {@code 1.1}.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -58,8 +58,8 @@ import java.lang.annotation.Target;
|
||||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||||
public @interface Since {
|
public @interface Since {
|
||||||
/**
|
/**
|
||||||
* The value indicating a version number since this member or type has been present.
|
* The value indicating a version number since this member or type has been present. The number is
|
||||||
* The number is inclusive; annotated elements will be included if {@code gsonVersion >= value}.
|
* inclusive; annotated elements will be included if {@code gsonVersion >= value}.
|
||||||
*/
|
*/
|
||||||
double value();
|
double value();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,15 +25,15 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation that indicates the version number until a member or a type should be present.
|
* An annotation that indicates the version number until a member or a type should be present.
|
||||||
* Basically, if Gson is created with a version number that is equal to or exceeds the value
|
* Basically, if Gson is created with a version number that is equal to or exceeds the value stored
|
||||||
* stored in the {@code Until} annotation then the field will be ignored from the JSON output.
|
* in the {@code Until} annotation then the field will be ignored from the JSON output. This
|
||||||
* This annotation is useful to manage versioning of your JSON classes for a web-service.
|
* annotation is useful to manage versioning of your JSON classes for a web-service.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>This annotation has no effect unless you build {@link com.google.gson.Gson} with a {@code
|
||||||
* This annotation has no effect unless you build {@link com.google.gson.Gson} with a
|
* GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
|
||||||
* {@code GsonBuilder} and invoke the {@link GsonBuilder#setVersion(double)} method.
|
*
|
||||||
|
* <p>Here is an example of how this annotation is meant to be used:
|
||||||
*
|
*
|
||||||
* <p>Here is an example of how this annotation is meant to be used:</p>
|
|
||||||
* <pre>
|
* <pre>
|
||||||
* public class User {
|
* public class User {
|
||||||
* private String firstName;
|
* private String firstName;
|
||||||
|
@ -45,11 +45,11 @@ import java.lang.annotation.Target;
|
||||||
*
|
*
|
||||||
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
|
* <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
|
||||||
* methods will use all the fields for serialization and deserialization. However, if you created
|
* methods will use all the fields for serialization and deserialization. However, if you created
|
||||||
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the
|
* Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the {@code
|
||||||
* {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress}
|
* toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress} and {@code
|
||||||
* and {@code password} fields from the example above, because the version number passed to the
|
* password} fields from the example above, because the version number passed to the GsonBuilder,
|
||||||
* GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation,
|
* {@code 1.2}, exceeds the version number set on the {@code Until} annotation, {@code 1.1}, for
|
||||||
* {@code 1.1}, for those fields.
|
* those fields.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -63,8 +63,8 @@ import java.lang.annotation.Target;
|
||||||
public @interface Until {
|
public @interface Until {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value indicating a version number until this member or type should be be included.
|
* The value indicating a version number until this member or type should be included. The number
|
||||||
* The number is exclusive; annotated elements will be included if {@code gsonVersion < value}.
|
* is exclusive; annotated elements will be included if {@code gsonVersion < value}.
|
||||||
*/
|
*/
|
||||||
double value();
|
double value();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This package provides annotations that can be used with {@link com.google.gson.Gson}.
|
* This package provides annotations that can be used with {@link com.google.gson.Gson}.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh, Joel Leitch
|
* @author Inderjeet Singh, Joel Leitch
|
||||||
*/
|
*/
|
||||||
package com.google.gson.annotations;
|
package com.google.gson.annotations;
|
||||||
|
|
|
@ -1,57 +1,57 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2008 Google Inc.
|
* Copyright (C) 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.gson.internal;
|
package com.google.gson.internal;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple utility class used to check method Preconditions.
|
* A simple utility class used to check method Preconditions.
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* public long divideBy(long value) {
|
* public long divideBy(long value) {
|
||||||
* Preconditions.checkArgument(value != 0);
|
* Preconditions.checkArgument(value != 0);
|
||||||
* return this.value / value;
|
* return this.value / value;
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
public final class $Gson$Preconditions {
|
public final class $Gson$Preconditions {
|
||||||
private $Gson$Preconditions() {
|
private $Gson$Preconditions() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated This is an internal Gson method. Use {@link Objects#requireNonNull(Object)}
|
||||||
* This is an internal Gson method. Use {@link Objects#requireNonNull(Object)} instead.
|
* instead.
|
||||||
*/
|
*/
|
||||||
// Only deprecated for now because external projects might be using this by accident
|
// Only deprecated for now because external projects might be using this by accident
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static <T> T checkNotNull(T obj) {
|
public static <T> T checkNotNull(T obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkArgument(boolean condition) {
|
public static void checkArgument(boolean condition) {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/*
|
||||||
* Copyright (C) 2008 Google Inc.
|
* Copyright (C) 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -50,8 +50,8 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new parameterized type, applying {@code typeArguments} to
|
* Returns a new parameterized type, applying {@code typeArguments} to {@code rawType} and
|
||||||
* {@code rawType} and enclosed by {@code ownerType}.
|
* enclosed by {@code ownerType}.
|
||||||
*
|
*
|
||||||
* @return a {@link java.io.Serializable serializable} parameterized type.
|
* @return a {@link java.io.Serializable serializable} parameterized type.
|
||||||
*/
|
*/
|
||||||
|
@ -61,8 +61,7 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array type whose elements are all instances of
|
* Returns an array type whose elements are all instances of {@code componentType}.
|
||||||
* {@code componentType}.
|
|
||||||
*
|
*
|
||||||
* @return a {@link java.io.Serializable serializable} generic array type.
|
* @return a {@link java.io.Serializable serializable} generic array type.
|
||||||
*/
|
*/
|
||||||
|
@ -71,40 +70,38 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a type that represents an unknown type that extends {@code bound}.
|
* Returns a type that represents an unknown type that extends {@code bound}. For example, if
|
||||||
* For example, if {@code bound} is {@code CharSequence.class}, this returns
|
* {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
|
||||||
* {@code ? extends CharSequence}. If {@code bound} is {@code Object.class},
|
* {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code ?
|
||||||
* this returns {@code ?}, which is shorthand for {@code ? extends Object}.
|
* extends Object}.
|
||||||
*/
|
*/
|
||||||
public static WildcardType subtypeOf(Type bound) {
|
public static WildcardType subtypeOf(Type bound) {
|
||||||
Type[] upperBounds;
|
Type[] upperBounds;
|
||||||
if (bound instanceof WildcardType) {
|
if (bound instanceof WildcardType) {
|
||||||
upperBounds = ((WildcardType) bound).getUpperBounds();
|
upperBounds = ((WildcardType) bound).getUpperBounds();
|
||||||
} else {
|
} else {
|
||||||
upperBounds = new Type[] { bound };
|
upperBounds = new Type[] {bound};
|
||||||
}
|
}
|
||||||
return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY);
|
return new WildcardTypeImpl(upperBounds, EMPTY_TYPE_ARRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a type that represents an unknown supertype of {@code bound}. For
|
* Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
|
||||||
* example, if {@code bound} is {@code String.class}, this returns {@code ?
|
* bound} is {@code String.class}, this returns {@code ? super String}.
|
||||||
* super String}.
|
|
||||||
*/
|
*/
|
||||||
public static WildcardType supertypeOf(Type bound) {
|
public static WildcardType supertypeOf(Type bound) {
|
||||||
Type[] lowerBounds;
|
Type[] lowerBounds;
|
||||||
if (bound instanceof WildcardType) {
|
if (bound instanceof WildcardType) {
|
||||||
lowerBounds = ((WildcardType) bound).getLowerBounds();
|
lowerBounds = ((WildcardType) bound).getLowerBounds();
|
||||||
} else {
|
} else {
|
||||||
lowerBounds = new Type[] { bound };
|
lowerBounds = new Type[] {bound};
|
||||||
}
|
}
|
||||||
return new WildcardTypeImpl(new Type[] { Object.class }, lowerBounds);
|
return new WildcardTypeImpl(new Type[] {Object.class}, lowerBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a type that is functionally equal but not necessarily equal
|
* Returns a type that is functionally equal but not necessarily equal according to {@link
|
||||||
* according to {@link Object#equals(Object) Object.equals()}. The returned
|
* Object#equals(Object) Object.equals()}. The returned type is {@link java.io.Serializable}.
|
||||||
* type is {@link java.io.Serializable}.
|
|
||||||
*/
|
*/
|
||||||
public static Type canonicalize(Type type) {
|
public static Type canonicalize(Type type) {
|
||||||
if (type instanceof Class) {
|
if (type instanceof Class) {
|
||||||
|
@ -113,8 +110,8 @@ public final class $Gson$Types {
|
||||||
|
|
||||||
} else if (type instanceof ParameterizedType) {
|
} else if (type instanceof ParameterizedType) {
|
||||||
ParameterizedType p = (ParameterizedType) type;
|
ParameterizedType p = (ParameterizedType) type;
|
||||||
return new ParameterizedTypeImpl(p.getOwnerType(),
|
return new ParameterizedTypeImpl(
|
||||||
p.getRawType(), p.getActualTypeArguments());
|
p.getOwnerType(), p.getRawType(), p.getActualTypeArguments());
|
||||||
|
|
||||||
} else if (type instanceof GenericArrayType) {
|
} else if (type instanceof GenericArrayType) {
|
||||||
GenericArrayType g = (GenericArrayType) type;
|
GenericArrayType g = (GenericArrayType) type;
|
||||||
|
@ -145,7 +142,7 @@ public final class $Gson$Types {
|
||||||
return (Class<?>) rawType;
|
return (Class<?>) rawType;
|
||||||
|
|
||||||
} else if (type instanceof GenericArrayType) {
|
} else if (type instanceof GenericArrayType) {
|
||||||
Type componentType = ((GenericArrayType)type).getGenericComponentType();
|
Type componentType = ((GenericArrayType) type).getGenericComponentType();
|
||||||
return Array.newInstance(getRawType(componentType), 0).getClass();
|
return Array.newInstance(getRawType(componentType), 0).getClass();
|
||||||
|
|
||||||
} else if (type instanceof TypeVariable) {
|
} else if (type instanceof TypeVariable) {
|
||||||
|
@ -161,8 +158,11 @@ public final class $Gson$Types {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
String className = type == null ? "null" : type.getClass().getName();
|
String className = type == null ? "null" : type.getClass().getName();
|
||||||
throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
|
throw new IllegalArgumentException(
|
||||||
+ "GenericArrayType, but <" + type + "> is of type " + className);
|
"Expected a Class, ParameterizedType, or GenericArrayType, but <"
|
||||||
|
+ type
|
||||||
|
+ "> is of type "
|
||||||
|
+ className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,9 +170,7 @@ public final class $Gson$Types {
|
||||||
return Objects.equals(a, b);
|
return Objects.equals(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns true if {@code a} and {@code b} are equal. */
|
||||||
* Returns true if {@code a} and {@code b} are equal.
|
|
||||||
*/
|
|
||||||
public static boolean equals(Type a, Type b) {
|
public static boolean equals(Type a, Type b) {
|
||||||
if (a == b) {
|
if (a == b) {
|
||||||
// also handles (a == null && b == null)
|
// also handles (a == null && b == null)
|
||||||
|
@ -219,7 +217,7 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
TypeVariable<?> va = (TypeVariable<?>) a;
|
TypeVariable<?> va = (TypeVariable<?>) a;
|
||||||
TypeVariable<?> vb = (TypeVariable<?>) b;
|
TypeVariable<?> vb = (TypeVariable<?>) b;
|
||||||
return va.getGenericDeclaration() == vb.getGenericDeclaration()
|
return Objects.equals(va.getGenericDeclaration(), vb.getGenericDeclaration())
|
||||||
&& va.getName().equals(vb.getName());
|
&& va.getName().equals(vb.getName());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -280,19 +278,23 @@ public final class $Gson$Types {
|
||||||
*/
|
*/
|
||||||
private static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
|
private static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
|
||||||
if (context instanceof WildcardType) {
|
if (context instanceof WildcardType) {
|
||||||
// wildcards are useless for resolving supertypes. As the upper bound has the same raw type, use it instead
|
// Wildcards are useless for resolving supertypes. As the upper bound has the same raw type,
|
||||||
Type[] bounds = ((WildcardType)context).getUpperBounds();
|
// use it instead
|
||||||
|
Type[] bounds = ((WildcardType) context).getUpperBounds();
|
||||||
// Currently the JLS only permits one bound for wildcards so using first bound is safe
|
// Currently the JLS only permits one bound for wildcards so using first bound is safe
|
||||||
assert bounds.length == 1;
|
assert bounds.length == 1;
|
||||||
context = bounds[0];
|
context = bounds[0];
|
||||||
}
|
}
|
||||||
checkArgument(supertype.isAssignableFrom(contextRawType));
|
checkArgument(supertype.isAssignableFrom(contextRawType));
|
||||||
return resolve(context, contextRawType,
|
return resolve(
|
||||||
|
context,
|
||||||
|
contextRawType,
|
||||||
$Gson$Types.getGenericSupertype(context, contextRawType, supertype));
|
$Gson$Types.getGenericSupertype(context, contextRawType, supertype));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the component type of this array type.
|
* Returns the component type of this array type.
|
||||||
|
*
|
||||||
* @throws ClassCastException if this type is not an array.
|
* @throws ClassCastException if this type is not an array.
|
||||||
*/
|
*/
|
||||||
public static Type getArrayComponentType(Type array) {
|
public static Type getArrayComponentType(Type array) {
|
||||||
|
@ -303,6 +305,7 @@ public final class $Gson$Types {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the element type of this collection type.
|
* Returns the element type of this collection type.
|
||||||
|
*
|
||||||
* @throws IllegalArgumentException if this type is not a collection.
|
* @throws IllegalArgumentException if this type is not a collection.
|
||||||
*/
|
*/
|
||||||
public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
|
public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
|
||||||
|
@ -315,8 +318,8 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a two element array containing this map's key and value types in
|
* Returns a two element array containing this map's key and value types in positions 0 and 1
|
||||||
* positions 0 and 1 respectively.
|
* respectively.
|
||||||
*/
|
*/
|
||||||
public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
|
public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
|
||||||
/*
|
/*
|
||||||
|
@ -325,7 +328,7 @@ public final class $Gson$Types {
|
||||||
* extend Hashtable<Object, Object>.
|
* extend Hashtable<Object, Object>.
|
||||||
*/
|
*/
|
||||||
if (context == Properties.class) {
|
if (context == Properties.class) {
|
||||||
return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties!
|
return new Type[] {String.class, String.class}; // TODO: test subclasses of Properties!
|
||||||
}
|
}
|
||||||
|
|
||||||
Type mapType = getSupertype(context, contextRawType, Map.class);
|
Type mapType = getSupertype(context, contextRawType, Map.class);
|
||||||
|
@ -334,7 +337,7 @@ public final class $Gson$Types {
|
||||||
ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
|
ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
|
||||||
return mapParameterizedType.getActualTypeArguments();
|
return mapParameterizedType.getActualTypeArguments();
|
||||||
}
|
}
|
||||||
return new Type[] { Object.class, Object.class };
|
return new Type[] {Object.class, Object.class};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
|
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
|
||||||
|
@ -342,8 +345,11 @@ public final class $Gson$Types {
|
||||||
return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable<?>, Type>());
|
return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable<?>, Type>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type resolve(Type context, Class<?> contextRawType, Type toResolve,
|
private static Type resolve(
|
||||||
Map<TypeVariable<?>, Type> visitedTypeVariables) {
|
Type context,
|
||||||
|
Class<?> contextRawType,
|
||||||
|
Type toResolve,
|
||||||
|
Map<TypeVariable<?>, Type> visitedTypeVariables) {
|
||||||
// this implementation is made a little more complicated in an attempt to avoid object-creation
|
// this implementation is made a little more complicated in an attempt to avoid object-creation
|
||||||
TypeVariable<?> resolving = null;
|
TypeVariable<?> resolving = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -369,19 +375,17 @@ public final class $Gson$Types {
|
||||||
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
|
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
|
||||||
Class<?> original = (Class<?>) toResolve;
|
Class<?> original = (Class<?>) toResolve;
|
||||||
Type componentType = original.getComponentType();
|
Type componentType = original.getComponentType();
|
||||||
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
Type newComponentType =
|
||||||
toResolve = equal(componentType, newComponentType)
|
resolve(context, contextRawType, componentType, visitedTypeVariables);
|
||||||
? original
|
toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType);
|
||||||
: arrayOf(newComponentType);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else if (toResolve instanceof GenericArrayType) {
|
} else if (toResolve instanceof GenericArrayType) {
|
||||||
GenericArrayType original = (GenericArrayType) toResolve;
|
GenericArrayType original = (GenericArrayType) toResolve;
|
||||||
Type componentType = original.getGenericComponentType();
|
Type componentType = original.getGenericComponentType();
|
||||||
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
Type newComponentType =
|
||||||
toResolve = equal(componentType, newComponentType)
|
resolve(context, contextRawType, componentType, visitedTypeVariables);
|
||||||
? original
|
toResolve = equal(componentType, newComponentType) ? original : arrayOf(newComponentType);
|
||||||
: arrayOf(newComponentType);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else if (toResolve instanceof ParameterizedType) {
|
} else if (toResolve instanceof ParameterizedType) {
|
||||||
|
@ -392,7 +396,8 @@ public final class $Gson$Types {
|
||||||
|
|
||||||
Type[] args = original.getActualTypeArguments();
|
Type[] args = original.getActualTypeArguments();
|
||||||
for (int t = 0, length = args.length; t < length; t++) {
|
for (int t = 0, length = args.length; t < length; t++) {
|
||||||
Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables);
|
Type resolvedTypeArgument =
|
||||||
|
resolve(context, contextRawType, args[t], visitedTypeVariables);
|
||||||
if (!equal(resolvedTypeArgument, args[t])) {
|
if (!equal(resolvedTypeArgument, args[t])) {
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
args = args.clone();
|
args = args.clone();
|
||||||
|
@ -402,9 +407,10 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toResolve = changed
|
toResolve =
|
||||||
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
|
changed
|
||||||
: original;
|
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
|
||||||
|
: original;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} else if (toResolve instanceof WildcardType) {
|
} else if (toResolve instanceof WildcardType) {
|
||||||
|
@ -413,13 +419,15 @@ public final class $Gson$Types {
|
||||||
Type[] originalUpperBound = original.getUpperBounds();
|
Type[] originalUpperBound = original.getUpperBounds();
|
||||||
|
|
||||||
if (originalLowerBound.length == 1) {
|
if (originalLowerBound.length == 1) {
|
||||||
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
|
Type lowerBound =
|
||||||
|
resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
|
||||||
if (lowerBound != originalLowerBound[0]) {
|
if (lowerBound != originalLowerBound[0]) {
|
||||||
toResolve = supertypeOf(lowerBound);
|
toResolve = supertypeOf(lowerBound);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (originalUpperBound.length == 1) {
|
} else if (originalUpperBound.length == 1) {
|
||||||
Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
|
Type upperBound =
|
||||||
|
resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
|
||||||
if (upperBound != originalUpperBound[0]) {
|
if (upperBound != originalUpperBound[0]) {
|
||||||
toResolve = subtypeOf(upperBound);
|
toResolve = subtypeOf(upperBound);
|
||||||
break;
|
break;
|
||||||
|
@ -439,7 +447,8 @@ public final class $Gson$Types {
|
||||||
return toResolve;
|
return toResolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
|
private static Type resolveTypeVariable(
|
||||||
|
Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
|
||||||
Class<?> declaredByRaw = declaringClassOf(unknown);
|
Class<?> declaredByRaw = declaringClassOf(unknown);
|
||||||
|
|
||||||
// we can't reduce this further
|
// we can't reduce this further
|
||||||
|
@ -471,9 +480,7 @@ public final class $Gson$Types {
|
||||||
*/
|
*/
|
||||||
private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
|
private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
|
||||||
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
|
GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
|
||||||
return genericDeclaration instanceof Class
|
return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
|
||||||
? (Class<?>) genericDeclaration
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void checkNotPrimitive(Type type) {
|
static void checkNotPrimitive(Type type) {
|
||||||
|
@ -496,9 +503,18 @@ public final class $Gson$Types {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here and below we put @SuppressWarnings("serial") on fields of type `Type`. Recent Java
|
||||||
|
// compilers complain that the declared type is not Serializable. But in this context we go out of
|
||||||
|
// our way to ensure that the Type in question is either Class (which is serializable) or one of
|
||||||
|
// the nested Type implementations here (which are also serializable).
|
||||||
private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
|
private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
|
||||||
|
@SuppressWarnings("serial")
|
||||||
private final Type ownerType;
|
private final Type ownerType;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
private final Type rawType;
|
private final Type rawType;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
private final Type[] typeArguments;
|
private final Type[] typeArguments;
|
||||||
|
|
||||||
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
|
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
|
||||||
|
@ -519,19 +535,23 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type[] getActualTypeArguments() {
|
@Override
|
||||||
|
public Type[] getActualTypeArguments() {
|
||||||
return typeArguments.clone();
|
return typeArguments.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type getRawType() {
|
@Override
|
||||||
|
public Type getRawType() {
|
||||||
return rawType;
|
return rawType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type getOwnerType() {
|
@Override
|
||||||
|
public Type getOwnerType() {
|
||||||
return ownerType;
|
return ownerType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean equals(Object other) {
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
return other instanceof ParameterizedType
|
return other instanceof ParameterizedType
|
||||||
&& $Gson$Types.equals(this, (ParameterizedType) other);
|
&& $Gson$Types.equals(this, (ParameterizedType) other);
|
||||||
}
|
}
|
||||||
|
@ -540,20 +560,23 @@ public final class $Gson$Types {
|
||||||
return o != null ? o.hashCode() : 0;
|
return o != null ? o.hashCode() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override
|
||||||
return Arrays.hashCode(typeArguments)
|
public int hashCode() {
|
||||||
^ rawType.hashCode()
|
return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType);
|
||||||
^ hashCodeOrZero(ownerType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
int length = typeArguments.length;
|
int length = typeArguments.length;
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return typeToString(rawType);
|
return typeToString(rawType);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));
|
StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));
|
||||||
stringBuilder.append(typeToString(rawType)).append("<").append(typeToString(typeArguments[0]));
|
stringBuilder
|
||||||
|
.append(typeToString(rawType))
|
||||||
|
.append("<")
|
||||||
|
.append(typeToString(typeArguments[0]));
|
||||||
for (int i = 1; i < length; i++) {
|
for (int i = 1; i < length; i++) {
|
||||||
stringBuilder.append(", ").append(typeToString(typeArguments[i]));
|
stringBuilder.append(", ").append(typeToString(typeArguments[i]));
|
||||||
}
|
}
|
||||||
|
@ -564,6 +587,7 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable {
|
private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable {
|
||||||
|
@SuppressWarnings("serial")
|
||||||
private final Type componentType;
|
private final Type componentType;
|
||||||
|
|
||||||
public GenericArrayTypeImpl(Type componentType) {
|
public GenericArrayTypeImpl(Type componentType) {
|
||||||
|
@ -571,20 +595,23 @@ public final class $Gson$Types {
|
||||||
this.componentType = canonicalize(componentType);
|
this.componentType = canonicalize(componentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type getGenericComponentType() {
|
@Override
|
||||||
|
public Type getGenericComponentType() {
|
||||||
return componentType;
|
return componentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean equals(Object o) {
|
@Override
|
||||||
return o instanceof GenericArrayType
|
public boolean equals(Object o) {
|
||||||
&& $Gson$Types.equals(this, (GenericArrayType) o);
|
return o instanceof GenericArrayType && $Gson$Types.equals(this, (GenericArrayType) o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
return componentType.hashCode();
|
return componentType.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
return typeToString(componentType) + "[]";
|
return typeToString(componentType) + "[]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,13 +619,16 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WildcardType interface supports multiple upper bounds and multiple
|
* The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
|
||||||
* lower bounds. We only support what the target Java version supports - at most one
|
* support what the target Java version supports - at most one bound, see also
|
||||||
* bound, see also https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound
|
* https://bugs.openjdk.java.net/browse/JDK-8250660. If a lower bound is set, the upper bound must
|
||||||
* is set, the upper bound must be Object.class.
|
* be Object.class.
|
||||||
*/
|
*/
|
||||||
private static final class WildcardTypeImpl implements WildcardType, Serializable {
|
private static final class WildcardTypeImpl implements WildcardType, Serializable {
|
||||||
|
@SuppressWarnings("serial")
|
||||||
private final Type upperBound;
|
private final Type upperBound;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
private final Type lowerBound;
|
private final Type lowerBound;
|
||||||
|
|
||||||
public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
|
public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
|
||||||
|
@ -620,26 +650,29 @@ public final class $Gson$Types {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type[] getUpperBounds() {
|
@Override
|
||||||
return new Type[] { upperBound };
|
public Type[] getUpperBounds() {
|
||||||
|
return new Type[] {upperBound};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Type[] getLowerBounds() {
|
@Override
|
||||||
return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY;
|
public Type[] getLowerBounds() {
|
||||||
|
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean equals(Object other) {
|
@Override
|
||||||
return other instanceof WildcardType
|
public boolean equals(Object other) {
|
||||||
&& $Gson$Types.equals(this, (WildcardType) other);
|
return other instanceof WildcardType && $Gson$Types.equals(this, (WildcardType) other);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
// this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());
|
// this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());
|
||||||
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1)
|
return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());
|
||||||
^ (31 + upperBound.hashCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
if (lowerBound != null) {
|
if (lowerBound != null) {
|
||||||
return "? super " + typeToString(lowerBound);
|
return "? super " + typeToString(lowerBound);
|
||||||
} else if (upperBound == Object.class) {
|
} else if (upperBound == Object.class) {
|
||||||
|
|
|
@ -47,23 +47,25 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ConcurrentNavigableMap;
|
import java.util.concurrent.ConcurrentNavigableMap;
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
|
|
||||||
/**
|
/** Returns a function that can construct an instance of a requested type. */
|
||||||
* Returns a function that can construct an instance of a requested type.
|
|
||||||
*/
|
|
||||||
public final class ConstructorConstructor {
|
public final class ConstructorConstructor {
|
||||||
private final Map<Type, InstanceCreator<?>> instanceCreators;
|
private final Map<Type, InstanceCreator<?>> instanceCreators;
|
||||||
private final boolean useJdkUnsafe;
|
private final boolean useJdkUnsafe;
|
||||||
private final List<ReflectionAccessFilter> reflectionFilters;
|
private final List<ReflectionAccessFilter> reflectionFilters;
|
||||||
|
|
||||||
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators, boolean useJdkUnsafe, List<ReflectionAccessFilter> reflectionFilters) {
|
public ConstructorConstructor(
|
||||||
|
Map<Type, InstanceCreator<?>> instanceCreators,
|
||||||
|
boolean useJdkUnsafe,
|
||||||
|
List<ReflectionAccessFilter> reflectionFilters) {
|
||||||
this.instanceCreators = instanceCreators;
|
this.instanceCreators = instanceCreators;
|
||||||
this.useJdkUnsafe = useJdkUnsafe;
|
this.useJdkUnsafe = useJdkUnsafe;
|
||||||
this.reflectionFilters = reflectionFilters;
|
this.reflectionFilters = reflectionFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the class can be instantiated by Unsafe allocator. If the instance has interface or abstract modifiers
|
* Check if the class can be instantiated by Unsafe allocator. If the instance has interface or
|
||||||
* return an exception message.
|
* abstract modifiers return an exception message.
|
||||||
|
*
|
||||||
* @param c instance of the class to be checked
|
* @param c instance of the class to be checked
|
||||||
* @return if instantiable {@code null}, else a non-{@code null} exception message
|
* @return if instantiable {@code null}, else a non-{@code null} exception message
|
||||||
*/
|
*/
|
||||||
|
@ -71,7 +73,8 @@ public final class ConstructorConstructor {
|
||||||
int modifiers = c.getModifiers();
|
int modifiers = c.getModifiers();
|
||||||
if (Modifier.isInterface(modifiers)) {
|
if (Modifier.isInterface(modifiers)) {
|
||||||
return "Interfaces can't be instantiated! Register an InstanceCreator"
|
return "Interfaces can't be instantiated! Register an InstanceCreator"
|
||||||
+ " or a TypeAdapter for this type. Interface name: " + c.getName();
|
+ " or a TypeAdapter for this type. Interface name: "
|
||||||
|
+ c.getName();
|
||||||
}
|
}
|
||||||
if (Modifier.isAbstract(modifiers)) {
|
if (Modifier.isAbstract(modifiers)) {
|
||||||
// R8 performs aggressive optimizations where it removes the default constructor of a class
|
// R8 performs aggressive optimizations where it removes the default constructor of a class
|
||||||
|
@ -83,8 +86,10 @@ public final class ConstructorConstructor {
|
||||||
* still making the class abstract
|
* still making the class abstract
|
||||||
*/
|
*/
|
||||||
return "Abstract classes can't be instantiated! Adjust the R8 configuration or register"
|
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()
|
+ " an InstanceCreator or a TypeAdapter for this type. Class name: "
|
||||||
+ "\nSee " + TroubleshootingGuide.createUrl("r8-abstract-class");
|
+ c.getName()
|
||||||
|
+ "\nSee "
|
||||||
|
+ TroubleshootingGuide.createUrl("r8-abstract-class");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +104,8 @@ public final class ConstructorConstructor {
|
||||||
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
|
final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
|
||||||
if (typeCreator != null) {
|
if (typeCreator != null) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return typeCreator.createInstance(type);
|
return typeCreator.createInstance(type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -107,11 +113,11 @@ public final class ConstructorConstructor {
|
||||||
|
|
||||||
// Next try raw type match for instance creators
|
// Next try raw type match for instance creators
|
||||||
@SuppressWarnings("unchecked") // types must agree
|
@SuppressWarnings("unchecked") // types must agree
|
||||||
final InstanceCreator<T> rawTypeCreator =
|
final InstanceCreator<T> rawTypeCreator = (InstanceCreator<T>) instanceCreators.get(rawType);
|
||||||
(InstanceCreator<T>) instanceCreators.get(rawType);
|
|
||||||
if (rawTypeCreator != null) {
|
if (rawTypeCreator != null) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return rawTypeCreator.createInstance(type);
|
return rawTypeCreator.createInstance(type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -125,7 +131,8 @@ public final class ConstructorConstructor {
|
||||||
return specialConstructor;
|
return specialConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType);
|
FilterResult filterResult =
|
||||||
|
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType);
|
||||||
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);
|
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);
|
||||||
if (defaultConstructor != null) {
|
if (defaultConstructor != null) {
|
||||||
return defaultConstructor;
|
return defaultConstructor;
|
||||||
|
@ -141,7 +148,8 @@ public final class ConstructorConstructor {
|
||||||
final String exceptionMessage = checkInstantiable(rawType);
|
final String exceptionMessage = checkInstantiable(rawType);
|
||||||
if (exceptionMessage != null) {
|
if (exceptionMessage != null) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
throw new JsonIOException(exceptionMessage);
|
throw new JsonIOException(exceptionMessage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -153,11 +161,15 @@ public final class ConstructorConstructor {
|
||||||
// finally try unsafe
|
// finally try unsafe
|
||||||
return newUnsafeAllocator(rawType);
|
return newUnsafeAllocator(rawType);
|
||||||
} else {
|
} else {
|
||||||
final String message = "Unable to create instance of " + rawType + "; ReflectionAccessFilter"
|
final String message =
|
||||||
+ " does not permit using reflection or Unsafe. Register an InstanceCreator or a TypeAdapter"
|
"Unable to create instance of "
|
||||||
+ " for this type or adjust the access filter to allow using reflection.";
|
+ 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>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
throw new JsonIOException(message);
|
throw new JsonIOException(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -165,17 +177,20 @@ public final class ConstructorConstructor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates constructors for special JDK collection types which do not have a public no-args constructor.
|
* Creates constructors for special JDK collection types which do not have a public no-args
|
||||||
|
* constructor.
|
||||||
*/
|
*/
|
||||||
private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(final Type type, Class<? super T> rawType) {
|
private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(
|
||||||
|
final Type type, Class<? super T> rawType) {
|
||||||
if (EnumSet.class.isAssignableFrom(rawType)) {
|
if (EnumSet.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
if (type instanceof ParameterizedType) {
|
if (type instanceof ParameterizedType) {
|
||||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||||
if (elementType instanceof Class) {
|
if (elementType instanceof Class) {
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
T set = (T) EnumSet.noneOf((Class)elementType);
|
T set = (T) EnumSet.noneOf((Class) elementType);
|
||||||
return set;
|
return set;
|
||||||
} else {
|
} else {
|
||||||
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
|
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
|
||||||
|
@ -190,7 +205,8 @@ public final class ConstructorConstructor {
|
||||||
// and constructor parameter might have completely different meaning
|
// and constructor parameter might have completely different meaning
|
||||||
else if (rawType == EnumMap.class) {
|
else if (rawType == EnumMap.class) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
if (type instanceof ParameterizedType) {
|
if (type instanceof ParameterizedType) {
|
||||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||||
if (elementType instanceof Class) {
|
if (elementType instanceof Class) {
|
||||||
|
@ -210,7 +226,8 @@ public final class ConstructorConstructor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType, FilterResult filterResult) {
|
private static <T> ObjectConstructor<T> newDefaultConstructor(
|
||||||
|
Class<? super T> rawType, FilterResult filterResult) {
|
||||||
// Cannot invoke constructor of abstract class
|
// Cannot invoke constructor of abstract class
|
||||||
if (Modifier.isAbstract(rawType.getModifiers())) {
|
if (Modifier.isAbstract(rawType.getModifiers())) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -223,17 +240,25 @@ public final class ConstructorConstructor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canAccess = filterResult == FilterResult.ALLOW || (ReflectionAccessFilterHelper.canAccess(constructor, null)
|
boolean canAccess =
|
||||||
// Be a bit more lenient here for BLOCK_ALL; if constructor is accessible and public then allow calling it
|
filterResult == FilterResult.ALLOW
|
||||||
&& (filterResult != FilterResult.BLOCK_ALL || Modifier.isPublic(constructor.getModifiers())));
|
|| (ReflectionAccessFilterHelper.canAccess(constructor, null)
|
||||||
|
// Be a bit more lenient here for BLOCK_ALL; if constructor is accessible and public
|
||||||
|
// then allow calling it
|
||||||
|
&& (filterResult != FilterResult.BLOCK_ALL
|
||||||
|
|| Modifier.isPublic(constructor.getModifiers())));
|
||||||
|
|
||||||
if (!canAccess) {
|
if (!canAccess) {
|
||||||
final String message = "Unable to invoke no-args constructor of " + rawType + ";"
|
final String message =
|
||||||
+ " constructor is not accessible and ReflectionAccessFilter does not permit making"
|
"Unable to invoke no-args constructor of "
|
||||||
+ " it accessible. Register an InstanceCreator or a TypeAdapter for this type, change"
|
+ rawType
|
||||||
+ " the visibility of the constructor or adjust the access filter.";
|
+ ";"
|
||||||
|
+ " 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>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
throw new JsonIOException(message);
|
throw new JsonIOException(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -265,22 +290,29 @@ public final class ConstructorConstructor {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked") // T is the same raw type as is requested
|
@SuppressWarnings("unchecked") // T is the same raw type as is requested
|
||||||
T newInstance = (T) constructor.newInstance();
|
T newInstance = (T) constructor.newInstance();
|
||||||
return newInstance;
|
return newInstance;
|
||||||
}
|
}
|
||||||
// Note: InstantiationException should be impossible because check at start of method made sure
|
// Note: InstantiationException should be impossible because check at start of method made
|
||||||
// that class is not abstract
|
// sure that class is not abstract
|
||||||
catch (InstantiationException e) {
|
catch (InstantiationException e) {
|
||||||
throw new RuntimeException("Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
|
throw new RuntimeException(
|
||||||
+ " with no args", e);
|
"Failed to invoke constructor '"
|
||||||
|
+ ReflectionHelper.constructorToString(constructor)
|
||||||
|
+ "' with no args",
|
||||||
|
e);
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
// TODO: don't wrap if cause is unchecked?
|
// TODO: don't wrap if cause is unchecked?
|
||||||
// TODO: JsonParseException ?
|
// TODO: JsonParseException ?
|
||||||
throw new RuntimeException("Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
|
throw new RuntimeException(
|
||||||
+ " with no args", e.getCause());
|
"Failed to invoke constructor '"
|
||||||
|
+ ReflectionHelper.constructorToString(constructor)
|
||||||
|
+ "' with no args",
|
||||||
|
e.getCause());
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
||||||
}
|
}
|
||||||
|
@ -288,10 +320,7 @@ public final class ConstructorConstructor {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Constructors for common interface types like Map and List and their subtypes. */
|
||||||
* Constructors for common interface types like Map and List and their
|
|
||||||
* subtypes.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
|
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
|
||||||
private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
|
private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
|
||||||
final Type type, Class<? super T> rawType) {
|
final Type type, Class<? super T> rawType) {
|
||||||
|
@ -307,25 +336,29 @@ public final class ConstructorConstructor {
|
||||||
if (Collection.class.isAssignableFrom(rawType)) {
|
if (Collection.class.isAssignableFrom(rawType)) {
|
||||||
if (SortedSet.class.isAssignableFrom(rawType)) {
|
if (SortedSet.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new TreeSet<>();
|
return (T) new TreeSet<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (Set.class.isAssignableFrom(rawType)) {
|
} else if (Set.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new LinkedHashSet<>();
|
return (T) new LinkedHashSet<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (Queue.class.isAssignableFrom(rawType)) {
|
} else if (Queue.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new ArrayDeque<>();
|
return (T) new ArrayDeque<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new ArrayList<>();
|
return (T) new ArrayList<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -335,32 +368,38 @@ public final class ConstructorConstructor {
|
||||||
if (Map.class.isAssignableFrom(rawType)) {
|
if (Map.class.isAssignableFrom(rawType)) {
|
||||||
if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
|
if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new ConcurrentSkipListMap<>();
|
return (T) new ConcurrentSkipListMap<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
|
} else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new ConcurrentHashMap<>();
|
return (T) new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (SortedMap.class.isAssignableFrom(rawType)) {
|
} else if (SortedMap.class.isAssignableFrom(rawType)) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new TreeMap<>();
|
return (T) new TreeMap<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else if (type instanceof ParameterizedType && !String.class.isAssignableFrom(
|
} else if (type instanceof ParameterizedType
|
||||||
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType())) {
|
&& !String.class.isAssignableFrom(
|
||||||
|
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType())) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new LinkedHashMap<>();
|
return (T) new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
return (T) new LinkedTreeMap<>();
|
return (T) new LinkedTreeMap<>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -373,41 +412,52 @@ public final class ConstructorConstructor {
|
||||||
private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) {
|
private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) {
|
||||||
if (useJdkUnsafe) {
|
if (useJdkUnsafe) {
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType);
|
T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType);
|
||||||
return newInstance;
|
return newInstance;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(("Unable to create instance of " + rawType + "."
|
throw new RuntimeException(
|
||||||
+ " Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args"
|
("Unable to create instance of "
|
||||||
+ " constructor may fix this problem."), e);
|
+ rawType
|
||||||
|
+ ". Registering an InstanceCreator or a TypeAdapter for this type, or adding a"
|
||||||
|
+ " no-args constructor may fix this problem."),
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
String exceptionMessage = "Unable to create instance of " + rawType + "; usage of JDK Unsafe"
|
String exceptionMessage =
|
||||||
+ " is disabled. Registering an InstanceCreator or a TypeAdapter for this type, adding a no-args"
|
"Unable to create instance of "
|
||||||
+ " constructor, or enabling usage of JDK Unsafe may fix this problem.";
|
+ 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
|
// Check if R8 removed all constructors
|
||||||
if (rawType.getDeclaredConstructors().length == 0) {
|
if (rawType.getDeclaredConstructors().length == 0) {
|
||||||
// R8 with Unsafe disabled might not be common enough to warrant a separate Troubleshooting Guide entry
|
// R8 with Unsafe disabled might not be common enough to warrant a separate Troubleshooting
|
||||||
exceptionMessage += " Or adjust your R8 configuration to keep the no-args constructor of the class.";
|
// 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
|
// Explicit final variable to allow usage in the anonymous class below
|
||||||
final String exceptionMessageF = exceptionMessage;
|
final String exceptionMessageF = exceptionMessage;
|
||||||
|
|
||||||
return new ObjectConstructor<T>() {
|
return new ObjectConstructor<T>() {
|
||||||
@Override public T construct() {
|
@Override
|
||||||
|
public T construct() {
|
||||||
throw new JsonIOException(exceptionMessageF);
|
throw new JsonIOException(exceptionMessageF);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
return instanceCreators.toString();
|
return instanceCreators.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.google.gson.TypeAdapterFactory;
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
import com.google.gson.annotations.Since;
|
import com.google.gson.annotations.Since;
|
||||||
import com.google.gson.annotations.Until;
|
import com.google.gson.annotations.Until;
|
||||||
|
import com.google.gson.internal.reflect.ReflectionHelper;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
@ -35,14 +36,12 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class selects which fields and types to omit. It is configurable,
|
* This class selects which fields and types to omit. It is configurable, supporting version
|
||||||
* supporting version attributes {@link Since} and {@link Until}, modifiers,
|
* attributes {@link Since} and {@link Until}, modifiers, synthetic fields, anonymous and local
|
||||||
* synthetic fields, anonymous and local classes, inner classes, and fields with
|
* classes, inner classes, and fields with the {@link Expose} annotation.
|
||||||
* the {@link Expose} annotation.
|
|
||||||
*
|
*
|
||||||
* <p>This class is a type adapter factory; types that are excluded will be
|
* <p>This class is a type adapter factory; types that are excluded will be adapted to null. It may
|
||||||
* adapted to null. It may delegate to another type adapter if only one
|
* delegate to another type adapter if only one direction is excluded.
|
||||||
* direction is excluded.
|
|
||||||
*
|
*
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
* @author Jesse Wilson
|
* @author Jesse Wilson
|
||||||
|
@ -58,7 +57,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
private List<ExclusionStrategy> serializationStrategies = Collections.emptyList();
|
private List<ExclusionStrategy> serializationStrategies = Collections.emptyList();
|
||||||
private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList();
|
private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList();
|
||||||
|
|
||||||
@Override protected Excluder clone() {
|
@Override
|
||||||
|
protected Excluder clone() {
|
||||||
try {
|
try {
|
||||||
return (Excluder) super.clone();
|
return (Excluder) super.clone();
|
||||||
} catch (CloneNotSupportedException e) {
|
} catch (CloneNotSupportedException e) {
|
||||||
|
@ -93,8 +93,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Excluder withExclusionStrategy(ExclusionStrategy exclusionStrategy,
|
public Excluder withExclusionStrategy(
|
||||||
boolean serialization, boolean deserialization) {
|
ExclusionStrategy exclusionStrategy, boolean serialization, boolean deserialization) {
|
||||||
Excluder result = clone();
|
Excluder result = clone();
|
||||||
if (serialization) {
|
if (serialization) {
|
||||||
result.serializationStrategies = new ArrayList<>(serializationStrategies);
|
result.serializationStrategies = new ArrayList<>(serializationStrategies);
|
||||||
|
@ -107,22 +107,26 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
|
||||||
Class<?> rawType = type.getRawType();
|
Class<?> rawType = type.getRawType();
|
||||||
boolean excludeClass = excludeClassChecks(rawType);
|
|
||||||
|
|
||||||
final boolean skipSerialize = excludeClass || excludeClassInStrategy(rawType, true);
|
final boolean skipSerialize = excludeClass(rawType, true);
|
||||||
final boolean skipDeserialize = excludeClass || excludeClassInStrategy(rawType, false);
|
final boolean skipDeserialize = excludeClass(rawType, false);
|
||||||
|
|
||||||
if (!skipSerialize && !skipDeserialize) {
|
if (!skipSerialize && !skipDeserialize) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TypeAdapter<T>() {
|
return new TypeAdapter<T>() {
|
||||||
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
|
/**
|
||||||
private TypeAdapter<T> delegate;
|
* 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;
|
||||||
|
|
||||||
@Override public T read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
if (skipDeserialize) {
|
if (skipDeserialize) {
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
return null;
|
return null;
|
||||||
|
@ -130,7 +134,8 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
return delegate().read(in);
|
return delegate().read(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
if (skipSerialize) {
|
if (skipSerialize) {
|
||||||
out.nullValue();
|
out.nullValue();
|
||||||
return;
|
return;
|
||||||
|
@ -139,10 +144,10 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeAdapter<T> delegate() {
|
private TypeAdapter<T> delegate() {
|
||||||
|
// A race might lead to `delegate` being assigned by multiple threads but the last
|
||||||
|
// assignment will stick
|
||||||
TypeAdapter<T> d = delegate;
|
TypeAdapter<T> d = delegate;
|
||||||
return d != null
|
return d != null ? d : (delegate = gson.getDelegateAdapter(Excluder.this, type));
|
||||||
? d
|
|
||||||
: (delegate = gson.getDelegateAdapter(Excluder.this, type));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -168,11 +173,7 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serializeInnerClasses && isInnerClass(field.getType())) {
|
if (excludeClass(field.getType(), serialize)) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAnonymousOrNonStaticLocal(field.getType())) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,44 +190,46 @@ public final class Excluder implements TypeAdapterFactory, Cloneable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean excludeClassChecks(Class<?> clazz) {
|
// public for unit tests; can otherwise be private
|
||||||
if (version != Excluder.IGNORE_VERSIONS && !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serializeInnerClasses && isInnerClass(clazz)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAnonymousOrNonStaticLocal(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean excludeClass(Class<?> clazz, boolean serialize) {
|
public boolean excludeClass(Class<?> clazz, boolean serialize) {
|
||||||
return excludeClassChecks(clazz) ||
|
if (version != Excluder.IGNORE_VERSIONS
|
||||||
excludeClassInStrategy(clazz, serialize);
|
&& !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean excludeClassInStrategy(Class<?> clazz, boolean serialize) {
|
if (!serializeInnerClasses && isInnerClass(clazz)) {
|
||||||
List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
|
return true;
|
||||||
for (ExclusionStrategy exclusionStrategy : list) {
|
}
|
||||||
if (exclusionStrategy.shouldSkipClass(clazz)) {
|
|
||||||
return true;
|
/*
|
||||||
}
|
* Exclude anonymous and local classes because they can have synthetic fields capturing enclosing
|
||||||
|
* values which makes serialization and deserialization unreliable.
|
||||||
|
* Don't exclude anonymous enum subclasses because enum types have a built-in adapter.
|
||||||
|
*
|
||||||
|
* Exclude only for deserialization; for serialization allow because custom adapter might be
|
||||||
|
* used; if no custom adapter exists reflection-based adapter otherwise excludes value.
|
||||||
|
*
|
||||||
|
* Cannot allow deserialization reliably here because some custom adapters like Collection adapter
|
||||||
|
* fall back to creating instances using Unsafe, which would likely lead to runtime exceptions
|
||||||
|
* for anonymous and local classes if they capture values.
|
||||||
|
*/
|
||||||
|
if (!serialize
|
||||||
|
&& !Enum.class.isAssignableFrom(clazz)
|
||||||
|
&& ReflectionHelper.isAnonymousOrNonStaticLocal(clazz)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
|
||||||
|
for (ExclusionStrategy exclusionStrategy : list) {
|
||||||
|
if (exclusionStrategy.shouldSkipClass(clazz)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAnonymousOrNonStaticLocal(Class<?> clazz) {
|
private static boolean isInnerClass(Class<?> clazz) {
|
||||||
return !Enum.class.isAssignableFrom(clazz) && !isStatic(clazz)
|
return clazz.isMemberClass() && !ReflectionHelper.isStatic(clazz);
|
||||||
&& (clazz.isAnonymousClass() || clazz.isLocalClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInnerClass(Class<?> clazz) {
|
|
||||||
return clazz.isMemberClass() && !isStatic(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStatic(Class<?> clazz) {
|
|
||||||
return (clazz.getModifiers() & Modifier.STATIC) != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidVersion(Since since, Until until) {
|
private boolean isValidVersion(Since since, Until until) {
|
||||||
|
|
|
@ -16,33 +16,33 @@
|
||||||
|
|
||||||
package com.google.gson.internal;
|
package com.google.gson.internal;
|
||||||
|
|
||||||
/**
|
/** Utility to check the major Java version of the current JVM. */
|
||||||
* Utility to check the major Java version of the current JVM.
|
|
||||||
*/
|
|
||||||
public final class JavaVersion {
|
public final class JavaVersion {
|
||||||
// Oracle defines naming conventions at http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
|
// Oracle defines naming conventions at
|
||||||
// However, many alternate implementations differ. For example, Debian used 9-debian as the version string
|
// http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
|
||||||
|
// However, many alternate implementations differ. For example, Debian used 9-debian as the
|
||||||
|
// version string
|
||||||
|
|
||||||
private static final int majorJavaVersion = determineMajorJavaVersion();
|
private static final int majorJavaVersion = determineMajorJavaVersion();
|
||||||
|
|
||||||
private static int determineMajorJavaVersion() {
|
private static int determineMajorJavaVersion() {
|
||||||
String javaVersion = System.getProperty("java.version");
|
String javaVersion = System.getProperty("java.version");
|
||||||
return getMajorJavaVersion(javaVersion);
|
return parseMajorJavaVersion(javaVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing only
|
// Visible for testing only
|
||||||
static int getMajorJavaVersion(String javaVersion) {
|
static int parseMajorJavaVersion(String javaVersion) {
|
||||||
int version = parseDotted(javaVersion);
|
int version = parseDotted(javaVersion);
|
||||||
if (version == -1) {
|
if (version == -1) {
|
||||||
version = extractBeginningInt(javaVersion);
|
version = extractBeginningInt(javaVersion);
|
||||||
}
|
}
|
||||||
if (version == -1) {
|
if (version == -1) {
|
||||||
return 6; // Choose minimum supported JDK version as default
|
return 6; // Choose minimum supported JDK version as default
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses both legacy 1.8 style and newer 9.0.4 style
|
// Parses both legacy 1.8 style and newer 9.0.4 style
|
||||||
private static int parseDotted(String javaVersion) {
|
private static int parseDotted(String javaVersion) {
|
||||||
try {
|
try {
|
||||||
String[] parts = javaVersion.split("[._]", 3);
|
String[] parts = javaVersion.split("[._]", 3);
|
||||||
|
@ -86,11 +86,12 @@ public final class JavaVersion {
|
||||||
/**
|
/**
|
||||||
* Gets a boolean value depending if the application is running on Java 9 or later
|
* Gets a boolean value depending if the application is running on Java 9 or later
|
||||||
*
|
*
|
||||||
* @return {@code true} if the application is running on Java 9 or later; and {@code false} otherwise.
|
* @return {@code true} if the application is running on Java 9 or later; and {@code false}
|
||||||
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
public static boolean isJava9OrLater() {
|
public static boolean isJava9OrLater() {
|
||||||
return majorJavaVersion >= 9;
|
return majorJavaVersion >= 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JavaVersion() { }
|
private JavaVersion() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,13 @@ package com.google.gson.internal;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/** Internal-only APIs of JsonReader available only to other classes in Gson. */
|
||||||
* Internal-only APIs of JsonReader available only to other classes in Gson.
|
|
||||||
*/
|
|
||||||
public abstract class JsonReaderInternalAccess {
|
public abstract class JsonReaderInternalAccess {
|
||||||
public static JsonReaderInternalAccess INSTANCE;
|
// Suppress warnings because field is initialized by `JsonReader` class during class loading
|
||||||
|
// (and therefore should be thread-safe), and any usage appears after `JsonReader` was loaded
|
||||||
|
@SuppressWarnings({"ConstantField", "NonFinalStaticField"})
|
||||||
|
public static volatile JsonReaderInternalAccess INSTANCE;
|
||||||
|
|
||||||
/**
|
/** Changes the type of the current property name token to a string value. */
|
||||||
* Changes the type of the current property name token to a string value.
|
|
||||||
*/
|
|
||||||
public abstract void promoteNameToValue(JsonReader reader) throws IOException;
|
public abstract void promoteNameToValue(JsonReader reader) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,17 @@ import java.math.BigDecimal;
|
||||||
public final class LazilyParsedNumber extends Number {
|
public final class LazilyParsedNumber extends Number {
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
/** @param value must not be null */
|
/**
|
||||||
|
* @param value must not be null
|
||||||
|
*/
|
||||||
public LazilyParsedNumber(String value) {
|
public LazilyParsedNumber(String value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BigDecimal asBigDecimal() {
|
||||||
|
return NumberLimits.parseBigDecimal(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int intValue() {
|
public int intValue() {
|
||||||
try {
|
try {
|
||||||
|
@ -43,7 +49,7 @@ public final class LazilyParsedNumber extends Number {
|
||||||
try {
|
try {
|
||||||
return (int) Long.parseLong(value);
|
return (int) Long.parseLong(value);
|
||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
return new BigDecimal(value).intValue();
|
return asBigDecimal().intValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +59,7 @@ public final class LazilyParsedNumber extends Number {
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(value);
|
return Long.parseLong(value);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return new BigDecimal(value).longValue();
|
return asBigDecimal().longValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,16 +79,16 @@ public final class LazilyParsedNumber extends Number {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If somebody is unlucky enough to have to serialize one of these, serialize
|
* If somebody is unlucky enough to have to serialize one of these, serialize it as a BigDecimal
|
||||||
* it as a BigDecimal so that they won't need Gson on the other side to
|
* so that they won't need Gson on the other side to deserialize it.
|
||||||
* deserialize it.
|
|
||||||
*/
|
*/
|
||||||
private Object writeReplace() throws ObjectStreamException {
|
private Object writeReplace() throws ObjectStreamException {
|
||||||
return new BigDecimal(value);
|
return asBigDecimal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readObject(ObjectInputStream in) throws IOException {
|
private void readObject(ObjectInputStream in) throws IOException {
|
||||||
// Don't permit directly deserializing this class; writeReplace() should have written a replacement
|
// Don't permit directly deserializing this class; writeReplace() should have written a
|
||||||
|
// replacement
|
||||||
throw new InvalidObjectException("Deserialization is unsupported");
|
throw new InvalidObjectException("Deserialization is unsupported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,24 +29,26 @@ import java.util.ConcurrentModificationException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of comparable keys to values. Unlike {@code TreeMap}, this class uses
|
* A map of comparable keys to values. Unlike {@code TreeMap}, this class uses insertion order for
|
||||||
* insertion order for iteration order. Comparison order is only used as an
|
* iteration order. Comparison order is only used as an optimization for efficient insertion and
|
||||||
* optimization for efficient insertion and removal.
|
* removal.
|
||||||
*
|
*
|
||||||
* <p>This implementation was derived from Android 4.1's TreeMap class.
|
* <p>This implementation was derived from Android 4.1's TreeMap class.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID
|
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID
|
||||||
public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
|
public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>>
|
@SuppressWarnings({"unchecked", "rawtypes"}) // to avoid Comparable<Comparable<Comparable<...>>>
|
||||||
private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
|
private static final Comparator<Comparable> NATURAL_ORDER =
|
||||||
@Override public int compare(Comparable a, Comparable b) {
|
new Comparator<Comparable>() {
|
||||||
return a.compareTo(b);
|
@Override
|
||||||
}
|
public int compare(Comparable a, Comparable b) {
|
||||||
};
|
return a.compareTo(b);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final Comparator<? super K> comparator;
|
private final Comparator<? super K> comparator;
|
||||||
private final boolean allowNullValues;
|
private final boolean allowNullValues;
|
||||||
|
@ -58,8 +60,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
final Node<K, V> header;
|
final Node<K, V> header;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a natural order, empty tree map whose keys must be mutually
|
* Create a natural order, empty tree map whose keys must be mutually comparable and non-null, and
|
||||||
* comparable and non-null, and whose values can be {@code null}.
|
* whose values can be {@code null}.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
|
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
|
||||||
public LinkedTreeMap() {
|
public LinkedTreeMap() {
|
||||||
|
@ -67,8 +69,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a natural order, empty tree map whose keys must be mutually
|
* Create a natural order, empty tree map whose keys must be mutually comparable and non-null.
|
||||||
* comparable and non-null.
|
|
||||||
*
|
*
|
||||||
* @param allowNullValues whether {@code null} is allowed as entry value
|
* @param allowNullValues whether {@code null} is allowed as entry value
|
||||||
*/
|
*/
|
||||||
|
@ -78,36 +79,39 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a tree map ordered by {@code comparator}. This map's keys may only
|
* Create a tree map ordered by {@code comparator}. This map's keys may only be null if {@code
|
||||||
* be null if {@code comparator} permits.
|
* comparator} permits.
|
||||||
*
|
*
|
||||||
* @param comparator the comparator to order elements with, or {@code null} to
|
* @param comparator the comparator to order elements with, or {@code null} to use the natural
|
||||||
* use the natural ordering.
|
* ordering.
|
||||||
* @param allowNullValues whether {@code null} is allowed as entry value
|
* @param allowNullValues whether {@code null} is allowed as entry value
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable
|
// unsafe! if comparator is null, this assumes K is comparable
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
public LinkedTreeMap(Comparator<? super K> comparator, boolean allowNullValues) {
|
public LinkedTreeMap(Comparator<? super K> comparator, boolean allowNullValues) {
|
||||||
this.comparator = comparator != null
|
this.comparator = comparator != null ? comparator : (Comparator) NATURAL_ORDER;
|
||||||
? comparator
|
|
||||||
: (Comparator) NATURAL_ORDER;
|
|
||||||
this.allowNullValues = allowNullValues;
|
this.allowNullValues = allowNullValues;
|
||||||
this.header = new Node<>(allowNullValues);
|
this.header = new Node<>(allowNullValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int size() {
|
@Override
|
||||||
|
public int size() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public V get(Object key) {
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
Node<K, V> node = findByObject(key);
|
Node<K, V> node = findByObject(key);
|
||||||
return node != null ? node.value : null;
|
return node != null ? node.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean containsKey(Object key) {
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
return findByObject(key) != null;
|
return findByObject(key) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public V put(K key, V value) {
|
@Override
|
||||||
|
public V put(K key, V value) {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
throw new NullPointerException("key == null");
|
throw new NullPointerException("key == null");
|
||||||
}
|
}
|
||||||
|
@ -120,7 +124,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void clear() {
|
@Override
|
||||||
|
public void clear() {
|
||||||
root = null;
|
root = null;
|
||||||
size = 0;
|
size = 0;
|
||||||
modCount++;
|
modCount++;
|
||||||
|
@ -130,7 +135,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
header.next = header.prev = header;
|
header.next = header.prev = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public V remove(Object key) {
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
Node<K, V> node = removeInternalByKey(key);
|
Node<K, V> node = removeInternalByKey(key);
|
||||||
return node != null ? node.value : null;
|
return node != null ? node.value : null;
|
||||||
}
|
}
|
||||||
|
@ -138,8 +144,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
/**
|
/**
|
||||||
* Returns the node at or adjacent to the given key, creating it if requested.
|
* Returns the node at or adjacent to the given key, creating it if requested.
|
||||||
*
|
*
|
||||||
* @throws ClassCastException if {@code key} and the tree's keys aren't
|
* @throws ClassCastException if {@code key} and the tree's keys aren't mutually comparable.
|
||||||
* mutually comparable.
|
|
||||||
*/
|
*/
|
||||||
Node<K, V> find(K key, boolean create) {
|
Node<K, V> find(K key, boolean create) {
|
||||||
Comparator<? super K> comparator = this.comparator;
|
Comparator<? super K> comparator = this.comparator;
|
||||||
|
@ -149,14 +154,14 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
if (nearest != null) {
|
if (nearest != null) {
|
||||||
// Micro-optimization: avoid polymorphic calls to Comparator.compare().
|
// Micro-optimization: avoid polymorphic calls to Comparator.compare().
|
||||||
@SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
|
@SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
|
||||||
Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
|
Comparable<Object> comparableKey =
|
||||||
? (Comparable<Object>) key
|
(comparator == NATURAL_ORDER) ? (Comparable<Object>) key : null;
|
||||||
: null;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
comparison = (comparableKey != null)
|
comparison =
|
||||||
? comparableKey.compareTo(nearest.key)
|
(comparableKey != null)
|
||||||
: comparator.compare(key, nearest.key);
|
? comparableKey.compareTo(nearest.key)
|
||||||
|
: comparator.compare(key, nearest.key);
|
||||||
|
|
||||||
// We found the requested key.
|
// We found the requested key.
|
||||||
if (comparison == 0) {
|
if (comparison == 0) {
|
||||||
|
@ -213,13 +218,12 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns this map's entry that has the same key and value as {@code
|
* Returns this map's entry that has the same key and value as {@code entry}, or null if this map
|
||||||
* entry}, or null if this map has no such entry.
|
* has no such entry.
|
||||||
*
|
*
|
||||||
* <p>This method uses the comparator for key equality rather than {@code
|
* <p>This method uses the comparator for key equality rather than {@code equals}. If this map's
|
||||||
* equals}. If this map's comparator isn't consistent with equals (such as
|
* comparator isn't consistent with equals (such as {@code String.CASE_INSENSITIVE_ORDER}), then
|
||||||
* {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code
|
* {@code remove()} and {@code contains()} will violate the collections API.
|
||||||
* contains()} will violate the collections API.
|
|
||||||
*/
|
*/
|
||||||
Node<K, V> findByEntry(Entry<?, ?> entry) {
|
Node<K, V> findByEntry(Entry<?, ?> entry) {
|
||||||
Node<K, V> mine = findByObject(entry.getKey());
|
Node<K, V> mine = findByObject(entry.getKey());
|
||||||
|
@ -227,13 +231,12 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return valuesEqual ? mine : null;
|
return valuesEqual ? mine : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean equal(Object a, Object b) {
|
private static boolean equal(Object a, Object b) {
|
||||||
return Objects.equals(a, b);
|
return Objects.equals(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes {@code node} from this tree, rearranging the tree's structure as
|
* Removes {@code node} from this tree, rearranging the tree's structure as necessary.
|
||||||
* necessary.
|
|
||||||
*
|
*
|
||||||
* @param unlink true to also unlink this node from the iteration linked list.
|
* @param unlink true to also unlink this node from the iteration linked list.
|
||||||
*/
|
*/
|
||||||
|
@ -325,11 +328,10 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rebalances the tree by making any AVL rotations necessary between the
|
* Rebalances the tree by making any AVL rotations necessary between the newly-unbalanced node and
|
||||||
* newly-unbalanced node and the tree's root.
|
* the tree's root.
|
||||||
*
|
*
|
||||||
* @param insert true if the node was unbalanced by an insert; false if it
|
* @param insert true if the node was unbalanced by an insert; false if it was by a removal.
|
||||||
* was by a removal.
|
|
||||||
*/
|
*/
|
||||||
private void rebalance(Node<K, V> unbalanced, boolean insert) {
|
private void rebalance(Node<K, V> unbalanced, boolean insert) {
|
||||||
for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
|
for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
|
||||||
|
@ -391,9 +393,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Rotates the subtree so that its root's right child is the new root. */
|
||||||
* Rotates the subtree so that its root's right child is the new root.
|
|
||||||
*/
|
|
||||||
private void rotateLeft(Node<K, V> root) {
|
private void rotateLeft(Node<K, V> root) {
|
||||||
Node<K, V> left = root.left;
|
Node<K, V> left = root.left;
|
||||||
Node<K, V> pivot = root.right;
|
Node<K, V> pivot = root.right;
|
||||||
|
@ -413,15 +413,12 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
root.parent = pivot;
|
root.parent = pivot;
|
||||||
|
|
||||||
// fix heights
|
// fix heights
|
||||||
root.height = Math.max(left != null ? left.height : 0,
|
root.height =
|
||||||
pivotLeft != null ? pivotLeft.height : 0) + 1;
|
Math.max(left != null ? left.height : 0, pivotLeft != null ? pivotLeft.height : 0) + 1;
|
||||||
pivot.height = Math.max(root.height,
|
pivot.height = Math.max(root.height, pivotRight != null ? pivotRight.height : 0) + 1;
|
||||||
pivotRight != null ? pivotRight.height : 0) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Rotates the subtree so that its root's left child is the new root. */
|
||||||
* Rotates the subtree so that its root's left child is the new root.
|
|
||||||
*/
|
|
||||||
private void rotateRight(Node<K, V> root) {
|
private void rotateRight(Node<K, V> root) {
|
||||||
Node<K, V> pivot = root.left;
|
Node<K, V> pivot = root.left;
|
||||||
Node<K, V> right = root.right;
|
Node<K, V> right = root.right;
|
||||||
|
@ -441,21 +438,22 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
root.parent = pivot;
|
root.parent = pivot;
|
||||||
|
|
||||||
// fixup heights
|
// fixup heights
|
||||||
root.height = Math.max(right != null ? right.height : 0,
|
root.height =
|
||||||
pivotRight != null ? pivotRight.height : 0) + 1;
|
Math.max(right != null ? right.height : 0, pivotRight != null ? pivotRight.height : 0) + 1;
|
||||||
pivot.height = Math.max(root.height,
|
pivot.height = Math.max(root.height, pivotLeft != null ? pivotLeft.height : 0) + 1;
|
||||||
pivotLeft != null ? pivotLeft.height : 0) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntrySet entrySet;
|
private EntrySet entrySet;
|
||||||
private KeySet keySet;
|
private KeySet keySet;
|
||||||
|
|
||||||
@Override public Set<Entry<K, V>> entrySet() {
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
EntrySet result = entrySet;
|
EntrySet result = entrySet;
|
||||||
return result != null ? result : (entrySet = new EntrySet());
|
return result != null ? result : (entrySet = new EntrySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Set<K> keySet() {
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
KeySet result = keySet;
|
KeySet result = keySet;
|
||||||
return result != null ? result : (keySet = new KeySet());
|
return result != null ? result : (keySet = new KeySet());
|
||||||
}
|
}
|
||||||
|
@ -490,15 +488,18 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
next.prev = this;
|
next.prev = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public K getKey() {
|
@Override
|
||||||
|
public K getKey() {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public V getValue() {
|
@Override
|
||||||
|
public V getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public V setValue(V value) {
|
@Override
|
||||||
|
public V setValue(V value) {
|
||||||
if (value == null && !allowNullValue) {
|
if (value == null && !allowNullValue) {
|
||||||
throw new NullPointerException("value == null");
|
throw new NullPointerException("value == null");
|
||||||
}
|
}
|
||||||
|
@ -507,7 +508,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return oldValue;
|
return oldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean equals(Object o) {
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
if (o instanceof Entry) {
|
if (o instanceof Entry) {
|
||||||
Entry<?, ?> other = (Entry<?, ?>) o;
|
Entry<?, ?> other = (Entry<?, ?>) o;
|
||||||
return (key == null ? other.getKey() == null : key.equals(other.getKey()))
|
return (key == null ? other.getKey() == null : key.equals(other.getKey()))
|
||||||
|
@ -516,18 +518,17 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override
|
||||||
return (key == null ? 0 : key.hashCode())
|
public int hashCode() {
|
||||||
^ (value == null ? 0 : value.hashCode());
|
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
return key + "=" + value;
|
return key + "=" + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the first node in this subtree. */
|
||||||
* Returns the first node in this subtree.
|
|
||||||
*/
|
|
||||||
public Node<K, V> first() {
|
public Node<K, V> first() {
|
||||||
Node<K, V> node = this;
|
Node<K, V> node = this;
|
||||||
Node<K, V> child = node.left;
|
Node<K, V> child = node.left;
|
||||||
|
@ -538,9 +539,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the last node in this subtree. */
|
||||||
* Returns the last node in this subtree.
|
|
||||||
*/
|
|
||||||
public Node<K, V> last() {
|
public Node<K, V> last() {
|
||||||
Node<K, V> node = this;
|
Node<K, V> node = this;
|
||||||
Node<K, V> child = node.right;
|
Node<K, V> child = node.right;
|
||||||
|
@ -557,8 +556,7 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
Node<K, V> lastReturned = null;
|
Node<K, V> lastReturned = null;
|
||||||
int expectedModCount = modCount;
|
int expectedModCount = modCount;
|
||||||
|
|
||||||
LinkedTreeMapIterator() {
|
LinkedTreeMapIterator() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
|
@ -579,7 +577,8 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return lastReturned = e;
|
return lastReturned = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final void remove() {
|
@Override
|
||||||
|
public final void remove() {
|
||||||
if (lastReturned == null) {
|
if (lastReturned == null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
@ -590,23 +589,28 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
}
|
}
|
||||||
|
|
||||||
class EntrySet extends AbstractSet<Entry<K, V>> {
|
class EntrySet extends AbstractSet<Entry<K, V>> {
|
||||||
@Override public int size() {
|
@Override
|
||||||
|
public int size() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Iterator<Entry<K, V>> iterator() {
|
@Override
|
||||||
|
public Iterator<Entry<K, V>> iterator() {
|
||||||
return new LinkedTreeMapIterator<Entry<K, V>>() {
|
return new LinkedTreeMapIterator<Entry<K, V>>() {
|
||||||
@Override public Entry<K, V> next() {
|
@Override
|
||||||
|
public Entry<K, V> next() {
|
||||||
return nextNode();
|
return nextNode();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean contains(Object o) {
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
|
return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean remove(Object o) {
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
if (!(o instanceof Entry)) {
|
if (!(o instanceof Entry)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -619,49 +623,56 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void clear() {
|
@Override
|
||||||
|
public void clear() {
|
||||||
LinkedTreeMap.this.clear();
|
LinkedTreeMap.this.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class KeySet extends AbstractSet<K> {
|
final class KeySet extends AbstractSet<K> {
|
||||||
@Override public int size() {
|
@Override
|
||||||
|
public int size() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Iterator<K> iterator() {
|
@Override
|
||||||
|
public Iterator<K> iterator() {
|
||||||
return new LinkedTreeMapIterator<K>() {
|
return new LinkedTreeMapIterator<K>() {
|
||||||
@Override public K next() {
|
@Override
|
||||||
|
public K next() {
|
||||||
return nextNode().key;
|
return nextNode().key;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean contains(Object o) {
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
return containsKey(o);
|
return containsKey(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean remove(Object key) {
|
@Override
|
||||||
|
public boolean remove(Object key) {
|
||||||
return removeInternalByKey(key) != null;
|
return removeInternalByKey(key) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void clear() {
|
@Override
|
||||||
|
public void clear() {
|
||||||
LinkedTreeMap.this.clear();
|
LinkedTreeMap.this.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If somebody is unlucky enough to have to serialize one of these, serialize
|
* If somebody is unlucky enough to have to serialize one of these, serialize it as a
|
||||||
* it as a LinkedHashMap so that they won't need Gson on the other side to
|
* LinkedHashMap so that they won't need Gson on the other side to deserialize it. Using
|
||||||
* deserialize it. Using serialization defeats our DoS defence, so most apps
|
* serialization defeats our DoS defence, so most apps shouldn't use it.
|
||||||
* shouldn't use it.
|
|
||||||
*/
|
*/
|
||||||
private Object writeReplace() throws ObjectStreamException {
|
private Object writeReplace() throws ObjectStreamException {
|
||||||
return new LinkedHashMap<>(this);
|
return new LinkedHashMap<>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readObject(ObjectInputStream in) throws IOException {
|
private void readObject(ObjectInputStream in) throws IOException {
|
||||||
// Don't permit directly deserializing this class; writeReplace() should have written a replacement
|
// Don't permit directly deserializing this class; writeReplace() should have written a
|
||||||
|
// replacement
|
||||||
throw new InvalidObjectException("Deserialization is unsupported");
|
throw new InvalidObjectException("Deserialization is unsupported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,9 @@ import java.util.Objects;
|
||||||
import java.util.RandomAccess;
|
import java.util.RandomAccess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link List} which wraps another {@code List} but prevents insertion of
|
* {@link List} which wraps another {@code List} but prevents insertion of {@code null} elements.
|
||||||
* {@code null} elements. Methods which only perform checks with the element
|
* Methods which only perform checks with the element argument (e.g. {@link #contains(Object)}) do
|
||||||
* argument (e.g. {@link #contains(Object)}) do not throw exceptions for
|
* not throw exceptions for {@code null} arguments.
|
||||||
* {@code null} arguments.
|
|
||||||
*/
|
*/
|
||||||
public class NonNullElementWrapperList<E> extends AbstractList<E> implements RandomAccess {
|
public class NonNullElementWrapperList<E> extends AbstractList<E> implements RandomAccess {
|
||||||
// Explicitly specify ArrayList as type to guarantee that delegate implements RandomAccess
|
// Explicitly specify ArrayList as type to guarantee that delegate implements RandomAccess
|
||||||
|
@ -38,11 +37,13 @@ public class NonNullElementWrapperList<E> extends AbstractList<E> implements Ran
|
||||||
this.delegate = Objects.requireNonNull(delegate);
|
this.delegate = Objects.requireNonNull(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public E get(int index) {
|
@Override
|
||||||
|
public E get(int index) {
|
||||||
return delegate.get(index);
|
return delegate.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int size() {
|
@Override
|
||||||
|
public int size() {
|
||||||
return delegate.size();
|
return delegate.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,61 +54,76 @@ public class NonNullElementWrapperList<E> extends AbstractList<E> implements Ran
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public E set(int index, E element) {
|
@Override
|
||||||
|
public E set(int index, E element) {
|
||||||
return delegate.set(index, nonNull(element));
|
return delegate.set(index, nonNull(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void add(int index, E element) {
|
@Override
|
||||||
|
public void add(int index, E element) {
|
||||||
delegate.add(index, nonNull(element));
|
delegate.add(index, nonNull(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public E remove(int index) {
|
@Override
|
||||||
|
public E remove(int index) {
|
||||||
return delegate.remove(index);
|
return delegate.remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The following methods are overridden because their default implementation is inefficient */
|
/* The following methods are overridden because their default implementation is inefficient */
|
||||||
|
|
||||||
@Override public void clear() {
|
@Override
|
||||||
|
public void clear() {
|
||||||
delegate.clear();
|
delegate.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean remove(Object o) {
|
@SuppressWarnings("UngroupedOverloads") // this is intentionally ungrouped, see comment above
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
return delegate.remove(o);
|
return delegate.remove(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean removeAll(Collection<?> c) {
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
return delegate.removeAll(c);
|
return delegate.removeAll(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean retainAll(Collection<?> c) {
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
return delegate.retainAll(c);
|
return delegate.retainAll(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean contains(Object o) {
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
return delegate.contains(o);
|
return delegate.contains(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int indexOf(Object o) {
|
@Override
|
||||||
|
public int indexOf(Object o) {
|
||||||
return delegate.indexOf(o);
|
return delegate.indexOf(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int lastIndexOf(Object o) {
|
@Override
|
||||||
|
public int lastIndexOf(Object o) {
|
||||||
return delegate.lastIndexOf(o);
|
return delegate.lastIndexOf(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Object[] toArray() {
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
return delegate.toArray();
|
return delegate.toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public <T> T[] toArray(T[] a) {
|
@Override
|
||||||
|
public <T> T[] toArray(T[] a) {
|
||||||
return delegate.toArray(a);
|
return delegate.toArray(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean equals(Object o) {
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
return delegate.equals(o);
|
return delegate.equals(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int hashCode() {
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
return delegate.hashCode();
|
return delegate.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.google.gson.internal;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class enforces limits on numbers parsed from JSON to avoid potential performance problems
|
||||||
|
* when extremely large numbers are used.
|
||||||
|
*/
|
||||||
|
public class NumberLimits {
|
||||||
|
private NumberLimits() {}
|
||||||
|
|
||||||
|
private static final int MAX_NUMBER_STRING_LENGTH = 10_000;
|
||||||
|
|
||||||
|
private static void checkNumberStringLength(String s) {
|
||||||
|
if (s.length() > MAX_NUMBER_STRING_LENGTH) {
|
||||||
|
throw new NumberFormatException("Number string too large: " + s.substring(0, 30) + "...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigDecimal parseBigDecimal(String s) throws NumberFormatException {
|
||||||
|
checkNumberStringLength(s);
|
||||||
|
BigDecimal decimal = new BigDecimal(s);
|
||||||
|
|
||||||
|
// Cast to long to avoid issues with abs when value is Integer.MIN_VALUE
|
||||||
|
if (Math.abs((long) decimal.scale()) >= 10_000) {
|
||||||
|
throw new NumberFormatException("Number has unsupported scale: " + s);
|
||||||
|
}
|
||||||
|
return decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger parseBigInteger(String s) throws NumberFormatException {
|
||||||
|
checkNumberStringLength(s);
|
||||||
|
return new BigInteger(s);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,17 +17,15 @@
|
||||||
package com.google.gson.internal;
|
package com.google.gson.internal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a generic object construction factory. The purpose of this class
|
* Defines a generic object construction factory. The purpose of this class is to construct a
|
||||||
* is to construct a default instance of a class that can be used for object
|
* default instance of a class that can be used for object navigation while deserialization from its
|
||||||
* navigation while deserialization from its JSON representation.
|
* JSON representation.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
public interface ObjectConstructor<T> {
|
public interface ObjectConstructor<T> {
|
||||||
|
|
||||||
/**
|
/** Returns a new instance. */
|
||||||
* Returns a new instance.
|
|
||||||
*/
|
|
||||||
public T construct();
|
public T construct();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,68 +19,46 @@ import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/** Provides DateFormats for US locale with patterns which were the default ones before Java 9. */
|
||||||
* Provides DateFormats for US locale with patterns which were the default ones before Java 9.
|
|
||||||
*/
|
|
||||||
public class PreJava9DateFormatProvider {
|
public class PreJava9DateFormatProvider {
|
||||||
|
private PreJava9DateFormatProvider() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the same DateFormat as {@code DateFormat.getDateInstance(style, Locale.US)} in Java 8 or below.
|
* Returns the same DateFormat as {@code DateFormat.getDateTimeInstance(dateStyle, timeStyle,
|
||||||
|
* Locale.US)} in Java 8 or below.
|
||||||
*/
|
*/
|
||||||
public static DateFormat getUSDateFormat(int style) {
|
public static DateFormat getUsDateTimeFormat(int dateStyle, int timeStyle) {
|
||||||
return new SimpleDateFormat(getDateFormatPattern(style), Locale.US);
|
String pattern =
|
||||||
}
|
getDatePartOfDateTimePattern(dateStyle) + " " + getTimePartOfDateTimePattern(timeStyle);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the same DateFormat as {@code DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US)}
|
|
||||||
* in Java 8 or below.
|
|
||||||
*/
|
|
||||||
public static DateFormat getUSDateTimeFormat(int dateStyle, int timeStyle) {
|
|
||||||
String pattern = getDatePartOfDateTimePattern(dateStyle) + " " + getTimePartOfDateTimePattern(timeStyle);
|
|
||||||
return new SimpleDateFormat(pattern, Locale.US);
|
return new SimpleDateFormat(pattern, Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getDateFormatPattern(int style) {
|
|
||||||
switch (style) {
|
|
||||||
case DateFormat.SHORT:
|
|
||||||
return "M/d/yy";
|
|
||||||
case DateFormat.MEDIUM:
|
|
||||||
return "MMM d, y";
|
|
||||||
case DateFormat.LONG:
|
|
||||||
return "MMMM d, y";
|
|
||||||
case DateFormat.FULL:
|
|
||||||
return "EEEE, MMMM d, y";
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unknown DateFormat style: " + style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getDatePartOfDateTimePattern(int dateStyle) {
|
private static String getDatePartOfDateTimePattern(int dateStyle) {
|
||||||
switch (dateStyle) {
|
switch (dateStyle) {
|
||||||
case DateFormat.SHORT:
|
case DateFormat.SHORT:
|
||||||
return "M/d/yy";
|
return "M/d/yy";
|
||||||
case DateFormat.MEDIUM:
|
case DateFormat.MEDIUM:
|
||||||
return "MMM d, yyyy";
|
return "MMM d, yyyy";
|
||||||
case DateFormat.LONG:
|
case DateFormat.LONG:
|
||||||
return "MMMM d, yyyy";
|
return "MMMM d, yyyy";
|
||||||
case DateFormat.FULL:
|
case DateFormat.FULL:
|
||||||
return "EEEE, MMMM d, yyyy";
|
return "EEEE, MMMM d, yyyy";
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown DateFormat style: " + dateStyle);
|
throw new IllegalArgumentException("Unknown DateFormat style: " + dateStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getTimePartOfDateTimePattern(int timeStyle) {
|
private static String getTimePartOfDateTimePattern(int timeStyle) {
|
||||||
switch (timeStyle) {
|
switch (timeStyle) {
|
||||||
case DateFormat.SHORT:
|
case DateFormat.SHORT:
|
||||||
return "h:mm a";
|
return "h:mm a";
|
||||||
case DateFormat.MEDIUM:
|
case DateFormat.MEDIUM:
|
||||||
return "h:mm:ss a";
|
return "h:mm:ss a";
|
||||||
case DateFormat.FULL:
|
case DateFormat.FULL:
|
||||||
case DateFormat.LONG:
|
case DateFormat.LONG:
|
||||||
return "h:mm:ss a z";
|
return "h:mm:ss a z";
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown DateFormat style: " + timeStyle);
|
throw new IllegalArgumentException("Unknown DateFormat style: " + timeStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,24 +19,22 @@ package com.google.gson.internal;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains static utility methods pertaining to primitive types and their
|
* Contains static utility methods pertaining to primitive types and their corresponding wrapper
|
||||||
* corresponding wrapper types.
|
* types.
|
||||||
*
|
*
|
||||||
* @author Kevin Bourrillion
|
* @author Kevin Bourrillion
|
||||||
*/
|
*/
|
||||||
public final class Primitives {
|
public final class Primitives {
|
||||||
private Primitives() {}
|
private Primitives() {}
|
||||||
|
|
||||||
/**
|
/** Returns true if this type is a primitive. */
|
||||||
* Returns true if this type is a primitive.
|
|
||||||
*/
|
|
||||||
public static boolean isPrimitive(Type type) {
|
public static boolean isPrimitive(Type type) {
|
||||||
return type instanceof Class<?> && ((Class<?>) type).isPrimitive();
|
return type instanceof Class<?> && ((Class<?>) type).isPrimitive();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if {@code type} is one of the nine
|
* Returns {@code true} if {@code type} is one of the nine primitive-wrapper types, such as {@link
|
||||||
* primitive-wrapper types, such as {@link Integer}.
|
* Integer}.
|
||||||
*
|
*
|
||||||
* @see Class#isPrimitive
|
* @see Class#isPrimitive
|
||||||
*/
|
*/
|
||||||
|
@ -53,15 +51,16 @@ public final class Primitives {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the corresponding wrapper type of {@code type} if it is a primitive
|
* Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise
|
||||||
* type; otherwise returns {@code type} itself. Idempotent.
|
* returns {@code type} itself. Idempotent.
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* wrap(int.class) == Integer.class
|
* wrap(int.class) == Integer.class
|
||||||
* wrap(Integer.class) == Integer.class
|
* wrap(Integer.class) == Integer.class
|
||||||
* wrap(String.class) == String.class
|
* wrap(String.class) == String.class
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings({"unchecked", "MissingBraces"})
|
||||||
public static <T> Class<T> wrap(Class<T> type) {
|
public static <T> Class<T> wrap(Class<T> type) {
|
||||||
if (type == int.class) return (Class<T>) Integer.class;
|
if (type == int.class) return (Class<T>) Integer.class;
|
||||||
if (type == float.class) return (Class<T>) Float.class;
|
if (type == float.class) return (Class<T>) Float.class;
|
||||||
|
@ -76,15 +75,16 @@ public final class Primitives {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the corresponding primitive type of {@code type} if it is a
|
* Returns the corresponding primitive type of {@code type} if it is a wrapper type; otherwise
|
||||||
* wrapper type; otherwise returns {@code type} itself. Idempotent.
|
* returns {@code type} itself. Idempotent.
|
||||||
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* unwrap(Integer.class) == int.class
|
* unwrap(Integer.class) == int.class
|
||||||
* unwrap(int.class) == int.class
|
* unwrap(int.class) == int.class
|
||||||
* unwrap(String.class) == String.class
|
* unwrap(String.class) == String.class
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings({"unchecked", "MissingBraces"})
|
||||||
public static <T> Class<T> unwrap(Class<T> type) {
|
public static <T> Class<T> unwrap(Class<T> type) {
|
||||||
if (type == Integer.class) return (Class<T>) int.class;
|
if (type == Integer.class) return (Class<T>) int.class;
|
||||||
if (type == Float.class) return (Class<T>) float.class;
|
if (type == Float.class) return (Class<T>) float.class;
|
||||||
|
|
|
@ -16,21 +16,19 @@
|
||||||
|
|
||||||
package com.google.gson.internal;
|
package com.google.gson.internal;
|
||||||
|
|
||||||
|
import com.google.gson.ReflectionAccessFilter;
|
||||||
|
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.AccessibleObject;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.google.gson.ReflectionAccessFilter;
|
/** Internal helper class for {@link ReflectionAccessFilter}. */
|
||||||
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal helper class for {@link ReflectionAccessFilter}.
|
|
||||||
*/
|
|
||||||
public class ReflectionAccessFilterHelper {
|
public class ReflectionAccessFilterHelper {
|
||||||
private ReflectionAccessFilterHelper() { }
|
private ReflectionAccessFilterHelper() {}
|
||||||
|
|
||||||
// Platform type detection is based on Moshi's Util.isPlatformType(Class)
|
// Platform type detection is based on Moshi's Util.isPlatformType(Class)
|
||||||
// See https://github.com/square/moshi/blob/3c108919ee1cce88a433ffda04eeeddc0341eae7/moshi/src/main/java/com/squareup/moshi/internal/Util.java#L141
|
// See
|
||||||
|
// https://github.com/square/moshi/blob/3c108919ee1cce88a433ffda04eeeddc0341eae7/moshi/src/main/java/com/squareup/moshi/internal/Util.java#L141
|
||||||
|
|
||||||
public static boolean isJavaType(Class<?> c) {
|
public static boolean isJavaType(Class<?> c) {
|
||||||
return isJavaType(c.getName());
|
return isJavaType(c.getName());
|
||||||
|
@ -53,17 +51,18 @@ public class ReflectionAccessFilterHelper {
|
||||||
public static boolean isAnyPlatformType(Class<?> c) {
|
public static boolean isAnyPlatformType(Class<?> c) {
|
||||||
String className = c.getName();
|
String className = c.getName();
|
||||||
return isAndroidType(className) // Covers Android and Java
|
return isAndroidType(className) // Covers Android and Java
|
||||||
|| className.startsWith("kotlin.")
|
|| className.startsWith("kotlin.")
|
||||||
|| className.startsWith("kotlinx.")
|
|| className.startsWith("kotlinx.")
|
||||||
|| className.startsWith("scala.");
|
|| className.startsWith("scala.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the result of applying all filters until the first one returns a result
|
* Gets the result of applying all filters until the first one returns a result other than {@link
|
||||||
* other than {@link FilterResult#INDECISIVE}, or {@link FilterResult#ALLOW} if
|
* FilterResult#INDECISIVE}, or {@link FilterResult#ALLOW} if the list of filters is empty or all
|
||||||
* the list of filters is empty or all returned {@code INDECISIVE}.
|
* returned {@code INDECISIVE}.
|
||||||
*/
|
*/
|
||||||
public static FilterResult getFilterResult(List<ReflectionAccessFilter> reflectionFilters, Class<?> c) {
|
public static FilterResult getFilterResult(
|
||||||
|
List<ReflectionAccessFilter> reflectionFilters, Class<?> c) {
|
||||||
for (ReflectionAccessFilter filter : reflectionFilters) {
|
for (ReflectionAccessFilter filter : reflectionFilters) {
|
||||||
FilterResult result = filter.check(c);
|
FilterResult result = filter.check(c);
|
||||||
if (result != FilterResult.INDECISIVE) {
|
if (result != FilterResult.INDECISIVE) {
|
||||||
|
@ -73,42 +72,46 @@ public class ReflectionAccessFilterHelper {
|
||||||
return FilterResult.ALLOW;
|
return FilterResult.ALLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** See {@link AccessibleObject#canAccess(Object)} (Java >= 9) */
|
||||||
* See {@link AccessibleObject#canAccess(Object)} (Java >= 9)
|
|
||||||
*/
|
|
||||||
public static boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
public static boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
||||||
return AccessChecker.INSTANCE.canAccess(accessibleObject, object);
|
return AccessChecker.INSTANCE.canAccess(accessibleObject, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static abstract class AccessChecker {
|
private abstract static class AccessChecker {
|
||||||
public static final AccessChecker INSTANCE;
|
public static final AccessChecker INSTANCE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
AccessChecker accessChecker = null;
|
AccessChecker accessChecker = null;
|
||||||
// TODO: Ideally should use Multi-Release JAR for this version specific code
|
// TODO: Ideally should use Multi-Release JAR for this version specific code
|
||||||
if (JavaVersion.isJava9OrLater()) {
|
if (JavaVersion.isJava9OrLater()) {
|
||||||
try {
|
try {
|
||||||
final Method canAccessMethod = AccessibleObject.class.getDeclaredMethod("canAccess", Object.class);
|
final Method canAccessMethod =
|
||||||
accessChecker = new AccessChecker() {
|
AccessibleObject.class.getDeclaredMethod("canAccess", Object.class);
|
||||||
@Override public boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
accessChecker =
|
||||||
try {
|
new AccessChecker() {
|
||||||
return (Boolean) canAccessMethod.invoke(accessibleObject, object);
|
@Override
|
||||||
} catch (Exception e) {
|
public boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
||||||
throw new RuntimeException("Failed invoking canAccess", e);
|
try {
|
||||||
}
|
return (Boolean) canAccessMethod.invoke(accessibleObject, object);
|
||||||
}
|
} catch (Exception e) {
|
||||||
};
|
throw new RuntimeException("Failed invoking canAccess", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
} catch (NoSuchMethodException ignored) {
|
} catch (NoSuchMethodException ignored) {
|
||||||
// OK: will assume everything is accessible
|
// OK: will assume everything is accessible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessChecker == null) {
|
if (accessChecker == null) {
|
||||||
accessChecker = new AccessChecker() {
|
accessChecker =
|
||||||
@Override public boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
new AccessChecker() {
|
||||||
// Cannot determine whether object can be accessed, so assume it can be accessed
|
@Override
|
||||||
return true;
|
public boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
||||||
}
|
// Cannot determine whether object can be accessed, so assume it can be accessed
|
||||||
};
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
INSTANCE = accessChecker;
|
INSTANCE = accessChecker;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,17 +31,13 @@ import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/** Reads and writes GSON parse trees over streams. */
|
||||||
* Reads and writes GSON parse trees over streams.
|
|
||||||
*/
|
|
||||||
public final class Streams {
|
public final class Streams {
|
||||||
private Streams() {
|
private Streams() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Takes a reader in any state and returns the next value as a JsonElement. */
|
||||||
* Takes a reader in any state and returns the next value as a JsonElement.
|
|
||||||
*/
|
|
||||||
public static JsonElement parse(JsonReader reader) throws JsonParseException {
|
public static JsonElement parse(JsonReader reader) throws JsonParseException {
|
||||||
boolean isEmpty = true;
|
boolean isEmpty = true;
|
||||||
try {
|
try {
|
||||||
|
@ -67,9 +63,7 @@ public final class Streams {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Writes the JSON element to the writer, recursively. */
|
||||||
* Writes the JSON element to the writer, recursively.
|
|
||||||
*/
|
|
||||||
public static void write(JsonElement element, JsonWriter writer) throws IOException {
|
public static void write(JsonElement element, JsonWriter writer) throws IOException {
|
||||||
TypeAdapters.JSON_ELEMENT.write(writer, element);
|
TypeAdapters.JSON_ELEMENT.write(writer, element);
|
||||||
}
|
}
|
||||||
|
@ -78,10 +72,7 @@ public final class Streams {
|
||||||
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
|
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer} is used. */
|
||||||
* Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer}
|
|
||||||
* is used.
|
|
||||||
*/
|
|
||||||
private static final class AppendableWriter extends Writer {
|
private static final class AppendableWriter extends Writer {
|
||||||
private final Appendable appendable;
|
private final Appendable appendable;
|
||||||
private final CurrentWrite currentWrite = new CurrentWrite();
|
private final CurrentWrite currentWrite = new CurrentWrite();
|
||||||
|
@ -90,40 +81,47 @@ public final class Streams {
|
||||||
this.appendable = appendable;
|
this.appendable = appendable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(char[] chars, int offset, int length) throws IOException {
|
@SuppressWarnings("UngroupedOverloads") // this is intentionally ungrouped, see comment below
|
||||||
|
@Override
|
||||||
|
public void write(char[] chars, int offset, int length) throws IOException {
|
||||||
currentWrite.setChars(chars);
|
currentWrite.setChars(chars);
|
||||||
appendable.append(currentWrite, offset, offset + length);
|
appendable.append(currentWrite, offset, offset + length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void flush() {}
|
@Override
|
||||||
@Override public void close() {}
|
public void flush() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {}
|
||||||
|
|
||||||
// Override these methods for better performance
|
// Override these methods for better performance
|
||||||
// They would otherwise unnecessarily create Strings or char arrays
|
// They would otherwise unnecessarily create Strings or char arrays
|
||||||
|
|
||||||
@Override public void write(int i) throws IOException {
|
@Override
|
||||||
|
public void write(int i) throws IOException {
|
||||||
appendable.append((char) i);
|
appendable.append((char) i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(String str, int off, int len) throws IOException {
|
@Override
|
||||||
|
public void write(String str, int off, int len) throws IOException {
|
||||||
// Appendable.append turns null -> "null", which is not desired here
|
// Appendable.append turns null -> "null", which is not desired here
|
||||||
Objects.requireNonNull(str);
|
Objects.requireNonNull(str);
|
||||||
appendable.append(str, off, off + len);
|
appendable.append(str, off, off + len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Writer append(CharSequence csq) throws IOException {
|
@Override
|
||||||
|
public Writer append(CharSequence csq) throws IOException {
|
||||||
appendable.append(csq);
|
appendable.append(csq);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Writer append(CharSequence csq, int start, int end) throws IOException {
|
@Override
|
||||||
|
public Writer append(CharSequence csq, int start, int end) throws IOException {
|
||||||
appendable.append(csq, start, end);
|
appendable.append(csq, start, end);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A mutable char sequence pointing at a single char[]. */
|
||||||
* A mutable char sequence pointing at a single char[].
|
|
||||||
*/
|
|
||||||
private static class CurrentWrite implements CharSequence {
|
private static class CurrentWrite implements CharSequence {
|
||||||
private char[] chars;
|
private char[] chars;
|
||||||
private String cachedString;
|
private String cachedString;
|
||||||
|
@ -133,18 +131,24 @@ public final class Streams {
|
||||||
this.cachedString = null;
|
this.cachedString = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int length() {
|
@Override
|
||||||
|
public int length() {
|
||||||
return chars.length;
|
return chars.length;
|
||||||
}
|
}
|
||||||
@Override public char charAt(int i) {
|
|
||||||
|
@Override
|
||||||
|
public char charAt(int i) {
|
||||||
return chars[i];
|
return chars[i];
|
||||||
}
|
}
|
||||||
@Override public CharSequence subSequence(int start, int end) {
|
|
||||||
|
@Override
|
||||||
|
public CharSequence subSequence(int start, int end) {
|
||||||
return new String(chars, start, end - start);
|
return new String(chars, start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must return string representation to satisfy toString() contract
|
// Must return string representation to satisfy toString() contract
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
if (cachedString == null) {
|
if (cachedString == null) {
|
||||||
cachedString = new String(chars);
|
cachedString = new String(chars);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ package com.google.gson.internal;
|
||||||
public class TroubleshootingGuide {
|
public class TroubleshootingGuide {
|
||||||
private TroubleshootingGuide() {}
|
private TroubleshootingGuide() {}
|
||||||
|
|
||||||
/**
|
/** Creates a URL referring to the specified troubleshooting section. */
|
||||||
* Creates a URL referring to the specified troubleshooting section.
|
|
||||||
*/
|
|
||||||
public static String createUrl(String id) {
|
public static String createUrl(String id) {
|
||||||
return "https://github.com/google/gson/blob/main/Troubleshooting.md#" + id;
|
return "https://github.com/google/gson/blob/main/Troubleshooting.md#" + id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,14 +31,15 @@ public abstract class UnsafeAllocator {
|
||||||
public abstract <T> T newInstance(Class<T> c) throws Exception;
|
public abstract <T> T newInstance(Class<T> c) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the class is instantiable. This check should have already occurred
|
* Asserts that the class is instantiable. This check should have already occurred in {@link
|
||||||
* in {@link ConstructorConstructor}; this check here acts as safeguard since trying
|
* ConstructorConstructor}; this check here acts as safeguard since trying to use Unsafe for
|
||||||
* to use Unsafe for non-instantiable classes might crash the JVM on some devices.
|
* non-instantiable classes might crash the JVM on some devices.
|
||||||
*/
|
*/
|
||||||
private static void assertInstantiable(Class<?> c) {
|
private static void assertInstantiable(Class<?> c) {
|
||||||
String exceptionMessage = ConstructorConstructor.checkInstantiable(c);
|
String exceptionMessage = ConstructorConstructor.checkInstantiable(c);
|
||||||
if (exceptionMessage != null) {
|
if (exceptionMessage != null) {
|
||||||
throw new AssertionError("UnsafeAllocator is used for non-instantiable type: " + exceptionMessage);
|
throw new AssertionError(
|
||||||
|
"UnsafeAllocator is used for non-instantiable type: " + exceptionMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,12 +74,12 @@ public abstract class UnsafeAllocator {
|
||||||
// private static native Object newInstance(Class<?> instantiationClass, int methodId);
|
// private static native Object newInstance(Class<?> instantiationClass, int methodId);
|
||||||
// }
|
// }
|
||||||
try {
|
try {
|
||||||
Method getConstructorId = ObjectStreamClass.class
|
Method getConstructorId =
|
||||||
.getDeclaredMethod("getConstructorId", Class.class);
|
ObjectStreamClass.class.getDeclaredMethod("getConstructorId", Class.class);
|
||||||
getConstructorId.setAccessible(true);
|
getConstructorId.setAccessible(true);
|
||||||
final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
|
final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
|
||||||
final Method newInstance = ObjectStreamClass.class
|
final Method newInstance =
|
||||||
.getDeclaredMethod("newInstance", Class.class, int.class);
|
ObjectStreamClass.class.getDeclaredMethod("newInstance", Class.class, int.class);
|
||||||
newInstance.setAccessible(true);
|
newInstance.setAccessible(true);
|
||||||
return new UnsafeAllocator() {
|
return new UnsafeAllocator() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,8 +99,8 @@ public abstract class UnsafeAllocator {
|
||||||
// Class<?> instantiationClass, Class<?> constructorClass);
|
// Class<?> instantiationClass, Class<?> constructorClass);
|
||||||
// }
|
// }
|
||||||
try {
|
try {
|
||||||
final Method newInstance = ObjectInputStream.class
|
final Method newInstance =
|
||||||
.getDeclaredMethod("newInstance", Class.class, Class.class);
|
ObjectInputStream.class.getDeclaredMethod("newInstance", Class.class, Class.class);
|
||||||
newInstance.setAccessible(true);
|
newInstance.setAccessible(true);
|
||||||
return new UnsafeAllocator() {
|
return new UnsafeAllocator() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,8 +118,11 @@ public abstract class UnsafeAllocator {
|
||||||
return new UnsafeAllocator() {
|
return new UnsafeAllocator() {
|
||||||
@Override
|
@Override
|
||||||
public <T> T newInstance(Class<T> c) {
|
public <T> T newInstance(Class<T> c) {
|
||||||
throw new UnsupportedOperationException("Cannot allocate " + c + ". Usage of JDK sun.misc.Unsafe is enabled, "
|
throw new UnsupportedOperationException(
|
||||||
+ "but it could not be used. Make sure your runtime is configured correctly.");
|
"Cannot allocate "
|
||||||
|
+ c
|
||||||
|
+ ". Usage of JDK sun.misc.Unsafe is enabled, but it could not be used."
|
||||||
|
+ " Make sure your runtime is configured correctly.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,37 +31,41 @@ import java.lang.reflect.GenericArrayType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/** Adapt an array of objects. */
|
||||||
* Adapt an array of objects.
|
|
||||||
*/
|
|
||||||
public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
|
public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
|
||||||
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
public static final TypeAdapterFactory FACTORY =
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
new TypeAdapterFactory() {
|
||||||
Type type = typeToken.getType();
|
@Override
|
||||||
if (!(type instanceof GenericArrayType || (type instanceof Class && ((Class<?>) type).isArray()))) {
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
return null;
|
Type type = typeToken.getType();
|
||||||
}
|
if (!(type instanceof GenericArrayType
|
||||||
|
|| (type instanceof Class && ((Class<?>) type).isArray()))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Type componentType = $Gson$Types.getArrayComponentType(type);
|
Type componentType = $Gson$Types.getArrayComponentType(type);
|
||||||
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
|
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
TypeAdapter<T> arrayAdapter = new ArrayTypeAdapter(
|
TypeAdapter<T> arrayAdapter =
|
||||||
gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
|
new ArrayTypeAdapter(
|
||||||
return arrayAdapter;
|
gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
|
||||||
}
|
return arrayAdapter;
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final Class<E> componentType;
|
private final Class<E> componentType;
|
||||||
private final TypeAdapter<E> componentTypeAdapter;
|
private final TypeAdapter<E> componentTypeAdapter;
|
||||||
|
|
||||||
public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
|
public ArrayTypeAdapter(
|
||||||
|
Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
|
||||||
this.componentTypeAdapter =
|
this.componentTypeAdapter =
|
||||||
new TypeAdapterRuntimeTypeWrapper<>(context, componentTypeAdapter, componentType);
|
new TypeAdapterRuntimeTypeWrapper<>(context, componentTypeAdapter, componentType);
|
||||||
this.componentType = componentType;
|
this.componentType = componentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Object read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Object read(JsonReader in) throws IOException {
|
||||||
if (in.peek() == JsonToken.NULL) {
|
if (in.peek() == JsonToken.NULL) {
|
||||||
in.nextNull();
|
in.nextNull();
|
||||||
return null;
|
return null;
|
||||||
|
@ -98,7 +102,8 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Object array) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, Object array) throws IOException {
|
||||||
if (array == null) {
|
if (array == null) {
|
||||||
out.nullValue();
|
out.nullValue();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -31,9 +31,7 @@ import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/** Adapt a homogeneous collection of objects. */
|
||||||
* Adapt a homogeneous collection of objects.
|
|
||||||
*/
|
|
||||||
public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final ConstructorConstructor constructorConstructor;
|
private final ConstructorConstructor constructorConstructor;
|
||||||
|
|
||||||
|
@ -63,7 +61,9 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final TypeAdapter<E> elementTypeAdapter;
|
private final TypeAdapter<E> elementTypeAdapter;
|
||||||
private final ObjectConstructor<? extends Collection<E>> constructor;
|
private final ObjectConstructor<? extends Collection<E>> constructor;
|
||||||
|
|
||||||
public Adapter(Gson context, Type elementType,
|
public Adapter(
|
||||||
|
Gson context,
|
||||||
|
Type elementType,
|
||||||
TypeAdapter<E> elementTypeAdapter,
|
TypeAdapter<E> elementTypeAdapter,
|
||||||
ObjectConstructor<? extends Collection<E>> constructor) {
|
ObjectConstructor<? extends Collection<E>> constructor) {
|
||||||
this.elementTypeAdapter =
|
this.elementTypeAdapter =
|
||||||
|
@ -71,7 +71,8 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Collection<E> read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Collection<E> read(JsonReader in) throws IOException {
|
||||||
if (in.peek() == JsonToken.NULL) {
|
if (in.peek() == JsonToken.NULL) {
|
||||||
in.nextNull();
|
in.nextNull();
|
||||||
return null;
|
return null;
|
||||||
|
@ -92,7 +93,8 @@ public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Collection<E> collection) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, Collection<E> collection) throws IOException {
|
||||||
if (collection == null) {
|
if (collection == null) {
|
||||||
out.nullValue();
|
out.nullValue();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2011 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.google.gson.internal.bind;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
|
||||||
import com.google.gson.TypeAdapter;
|
|
||||||
import com.google.gson.TypeAdapterFactory;
|
|
||||||
import com.google.gson.internal.JavaVersion;
|
|
||||||
import com.google.gson.internal.PreJava9DateFormatProvider;
|
|
||||||
import com.google.gson.util.ISO8601Utils;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
import com.google.gson.stream.JsonToken;
|
|
||||||
import com.google.gson.stream.JsonWriter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.ParsePosition;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter for Date. Although this class appears stateless, it is not.
|
|
||||||
* DateFormat captures its time zone and locale when it is created, which gives
|
|
||||||
* this class state. DateFormat isn't thread safe either, so this class has
|
|
||||||
* to synchronize its read and write methods.
|
|
||||||
*/
|
|
||||||
public final class DateTypeAdapter extends TypeAdapter<Date> {
|
|
||||||
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
|
||||||
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
|
||||||
return typeToken.getRawType() == Date.class ? (TypeAdapter<T>) new DateTypeAdapter() : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of 1 or more different date formats used for de-serialization attempts.
|
|
||||||
* The first of them (default US format) is used for serialization as well.
|
|
||||||
*/
|
|
||||||
private final List<DateFormat> dateFormats = new ArrayList<>();
|
|
||||||
|
|
||||||
public DateTypeAdapter() {
|
|
||||||
dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US));
|
|
||||||
if (!Locale.getDefault().equals(Locale.US)) {
|
|
||||||
dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
|
|
||||||
}
|
|
||||||
if (JavaVersion.isJava9OrLater()) {
|
|
||||||
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Date read(JsonReader in) throws IOException {
|
|
||||||
if (in.peek() == JsonToken.NULL) {
|
|
||||||
in.nextNull();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return deserializeToDate(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Date deserializeToDate(JsonReader in) throws IOException {
|
|
||||||
String s = in.nextString();
|
|
||||||
synchronized (dateFormats) {
|
|
||||||
for (DateFormat dateFormat : dateFormats) {
|
|
||||||
try {
|
|
||||||
return dateFormat.parse(s);
|
|
||||||
} catch (ParseException ignored) {
|
|
||||||
// OK: try the next format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return ISO8601Utils.parse(s, new ParsePosition(0));
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Date value) throws IOException {
|
|
||||||
if (value == null) {
|
|
||||||
out.nullValue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateFormat dateFormat = dateFormats.get(0);
|
|
||||||
String dateFormatAsString;
|
|
||||||
synchronized (dateFormats) {
|
|
||||||
dateFormatAsString = dateFormat.format(value);
|
|
||||||
}
|
|
||||||
out.value(dateFormatAsString);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,12 +16,14 @@
|
||||||
|
|
||||||
package com.google.gson.internal.bind;
|
package com.google.gson.internal.bind;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
import com.google.gson.TypeAdapterFactory;
|
import com.google.gson.TypeAdapterFactory;
|
||||||
import com.google.gson.internal.JavaVersion;
|
import com.google.gson.internal.JavaVersion;
|
||||||
import com.google.gson.internal.PreJava9DateFormatProvider;
|
import com.google.gson.internal.PreJava9DateFormatProvider;
|
||||||
import com.google.gson.util.ISO8601Utils;
|
import com.google.gson.util.ISO8601Utils;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
@ -35,11 +37,17 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This type adapter supports subclasses of date by defining a
|
* This type adapter supports subclasses of date by defining a {@link
|
||||||
* {@link DefaultDateTypeAdapter.DateType} and then using its {@code createAdapterFactory}
|
* DefaultDateTypeAdapter.DateType} and then using its {@code createAdapterFactory} methods.
|
||||||
* methods.
|
*
|
||||||
|
* <p><b>Important:</b> Instances of this class (or rather the {@link SimpleDateFormat} they use)
|
||||||
|
* capture the current default {@link Locale} and {@link TimeZone} when they are created. Therefore
|
||||||
|
* avoid storing factories obtained from {@link DateType} in {@code static} fields, since they only
|
||||||
|
* create a single adapter instance and its behavior would then depend on when Gson classes are
|
||||||
|
* loaded first, and which default {@code Locale} and {@code TimeZone} was used at that point.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh
|
* @author Inderjeet Singh
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
|
@ -47,12 +55,38 @@ import java.util.Objects;
|
||||||
public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T> {
|
public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T> {
|
||||||
private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";
|
private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";
|
||||||
|
|
||||||
public static abstract class DateType<T extends Date> {
|
/** Factory for {@link Date} adapters which use {@link DateFormat#DEFAULT} as style. */
|
||||||
public static final DateType<Date> DATE = new DateType<Date>(Date.class) {
|
public static final TypeAdapterFactory DEFAULT_STYLE_FACTORY =
|
||||||
@Override protected Date deserialize(Date date) {
|
// Because SimpleDateFormat captures the default TimeZone when it was created, let the factory
|
||||||
return date;
|
// always create new DefaultDateTypeAdapter instances (which are then cached by the Gson
|
||||||
}
|
// instances) instead of having a single static DefaultDateTypeAdapter instance
|
||||||
};
|
// Otherwise the behavior would depend on when an application first loads Gson classes and
|
||||||
|
// which default TimeZone is set at that point, which would be quite brittle
|
||||||
|
new TypeAdapterFactory() {
|
||||||
|
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
||||||
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
|
return typeToken.getRawType() == Date.class
|
||||||
|
? (TypeAdapter<T>)
|
||||||
|
new DefaultDateTypeAdapter<>(
|
||||||
|
DateType.DATE, DateFormat.DEFAULT, DateFormat.DEFAULT)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DefaultDateTypeAdapter#DEFAULT_STYLE_FACTORY";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract static class DateType<T extends Date> {
|
||||||
|
public static final DateType<Date> DATE =
|
||||||
|
new DateType<Date>(Date.class) {
|
||||||
|
@Override
|
||||||
|
protected Date deserialize(Date date) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final Class<T> dateClass;
|
private final Class<T> dateClass;
|
||||||
|
|
||||||
|
@ -70,24 +104,16 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
|
||||||
return createFactory(new DefaultDateTypeAdapter<>(this, datePattern));
|
return createFactory(new DefaultDateTypeAdapter<>(this, datePattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TypeAdapterFactory createAdapterFactory(int style) {
|
|
||||||
return createFactory(new DefaultDateTypeAdapter<>(this, style));
|
|
||||||
}
|
|
||||||
|
|
||||||
public final TypeAdapterFactory createAdapterFactory(int dateStyle, int timeStyle) {
|
public final TypeAdapterFactory createAdapterFactory(int dateStyle, int timeStyle) {
|
||||||
return createFactory(new DefaultDateTypeAdapter<>(this, dateStyle, timeStyle));
|
return createFactory(new DefaultDateTypeAdapter<>(this, dateStyle, timeStyle));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final TypeAdapterFactory createDefaultsAdapterFactory() {
|
|
||||||
return createFactory(new DefaultDateTypeAdapter<>(this, DateFormat.DEFAULT, DateFormat.DEFAULT));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final DateType<T> dateType;
|
private final DateType<T> dateType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of 1 or more different date formats used for de-serialization attempts.
|
* List of 1 or more different date formats used for de-serialization attempts. The first of them
|
||||||
* The first of them is used for serialization as well.
|
* is used for serialization as well.
|
||||||
*/
|
*/
|
||||||
private final List<DateFormat> dateFormats = new ArrayList<>();
|
private final List<DateFormat> dateFormats = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -99,17 +125,6 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultDateTypeAdapter(DateType<T> dateType, int style) {
|
|
||||||
this.dateType = Objects.requireNonNull(dateType);
|
|
||||||
dateFormats.add(DateFormat.getDateInstance(style, Locale.US));
|
|
||||||
if (!Locale.getDefault().equals(Locale.US)) {
|
|
||||||
dateFormats.add(DateFormat.getDateInstance(style));
|
|
||||||
}
|
|
||||||
if (JavaVersion.isJava9OrLater()) {
|
|
||||||
dateFormats.add(PreJava9DateFormatProvider.getUSDateFormat(style));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefaultDateTypeAdapter(DateType<T> dateType, int dateStyle, int timeStyle) {
|
private DefaultDateTypeAdapter(DateType<T> dateType, int dateStyle, int timeStyle) {
|
||||||
this.dateType = Objects.requireNonNull(dateType);
|
this.dateType = Objects.requireNonNull(dateType);
|
||||||
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US));
|
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US));
|
||||||
|
@ -117,12 +132,10 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
|
||||||
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle));
|
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle));
|
||||||
}
|
}
|
||||||
if (JavaVersion.isJava9OrLater()) {
|
if (JavaVersion.isJava9OrLater()) {
|
||||||
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(dateStyle, timeStyle));
|
dateFormats.add(PreJava9DateFormatProvider.getUsDateTimeFormat(dateStyle, timeStyle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// These methods need to be synchronized since JDK DateFormat classes are not thread-safe
|
|
||||||
// See issue 162
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, Date value) throws IOException {
|
public void write(JsonWriter out, Date value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -132,6 +145,7 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
|
||||||
|
|
||||||
DateFormat dateFormat = dateFormats.get(0);
|
DateFormat dateFormat = dateFormats.get(0);
|
||||||
String dateFormatAsString;
|
String dateFormatAsString;
|
||||||
|
// Needs to be synchronized since JDK DateFormat classes are not thread-safe
|
||||||
synchronized (dateFormats) {
|
synchronized (dateFormats) {
|
||||||
dateFormatAsString = dateFormat.format(value);
|
dateFormatAsString = dateFormat.format(value);
|
||||||
}
|
}
|
||||||
|
@ -150,12 +164,16 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
|
||||||
|
|
||||||
private Date deserializeToDate(JsonReader in) throws IOException {
|
private Date deserializeToDate(JsonReader in) throws IOException {
|
||||||
String s = in.nextString();
|
String s = in.nextString();
|
||||||
|
// Needs to be synchronized since JDK DateFormat classes are not thread-safe
|
||||||
synchronized (dateFormats) {
|
synchronized (dateFormats) {
|
||||||
for (DateFormat dateFormat : dateFormats) {
|
for (DateFormat dateFormat : dateFormats) {
|
||||||
|
TimeZone originalTimeZone = dateFormat.getTimeZone();
|
||||||
try {
|
try {
|
||||||
return dateFormat.parse(s);
|
return dateFormat.parse(s);
|
||||||
} catch (ParseException ignored) {
|
} catch (ParseException ignored) {
|
||||||
// OK: try the next format
|
// OK: try the next format
|
||||||
|
} finally {
|
||||||
|
dateFormat.setTimeZone(originalTimeZone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,7 +181,8 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
|
||||||
try {
|
try {
|
||||||
return ISO8601Utils.parse(s, new ParsePosition(0));
|
return ISO8601Utils.parse(s, new ParsePosition(0));
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
|
throw new JsonSyntaxException(
|
||||||
|
"Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,29 +36,26 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
*/
|
*/
|
||||||
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
|
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private static class DummyTypeAdapterFactory implements TypeAdapterFactory {
|
private static class DummyTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
throw new AssertionError("Factory should not be used");
|
throw new AssertionError("Factory should not be used");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} on a class. */
|
||||||
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter}
|
private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY =
|
||||||
* on a class.
|
new DummyTypeAdapterFactory();
|
||||||
*/
|
|
||||||
private static final TypeAdapterFactory TREE_TYPE_CLASS_DUMMY_FACTORY = new DummyTypeAdapterFactory();
|
|
||||||
|
|
||||||
/**
|
/** Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter} on a field. */
|
||||||
* Factory used for {@link TreeTypeAdapter}s created for {@code @JsonAdapter}
|
private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY =
|
||||||
* on a field.
|
new DummyTypeAdapterFactory();
|
||||||
*/
|
|
||||||
private static final TypeAdapterFactory TREE_TYPE_FIELD_DUMMY_FACTORY = new DummyTypeAdapterFactory();
|
|
||||||
|
|
||||||
private final ConstructorConstructor constructorConstructor;
|
private final ConstructorConstructor constructorConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For a class, if it is annotated with {@code @JsonAdapter} and refers to a {@link TypeAdapterFactory},
|
* For a class, if it is annotated with {@code @JsonAdapter} and refers to a {@link
|
||||||
* stores the factory instance in case it has been requested already.
|
* TypeAdapterFactory}, stores the factory instance in case it has been requested already. Has to
|
||||||
* Has to be a {@link ConcurrentMap} because {@link Gson} guarantees to be thread-safe.
|
* be a {@link ConcurrentMap} because {@link Gson} guarantees to be thread-safe.
|
||||||
*/
|
*/
|
||||||
// Note: In case these strong reference to TypeAdapterFactory instances are considered
|
// Note: In case these strong reference to TypeAdapterFactory instances are considered
|
||||||
// a memory leak in the future, could consider switching to WeakReference<TypeAdapterFactory>
|
// a memory leak in the future, could consider switching to WeakReference<TypeAdapterFactory>
|
||||||
|
@ -70,11 +67,12 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate helper method to make sure callers retrieve annotation in a consistent way
|
// Separate helper method to make sure callers retrieve annotation in a consistent way
|
||||||
private JsonAdapter getAnnotation(Class<?> rawType) {
|
private static JsonAdapter getAnnotation(Class<?> rawType) {
|
||||||
return rawType.getAnnotation(JsonAdapter.class);
|
return rawType.getAnnotation(JsonAdapter.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // this is not safe; requires that user has specified correct adapter class for @JsonAdapter
|
// this is not safe; requires that user has specified correct adapter class for @JsonAdapter
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
|
||||||
Class<? super T> rawType = targetType.getRawType();
|
Class<? super T> rawType = targetType.getRawType();
|
||||||
|
@ -82,13 +80,16 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation, true);
|
return (TypeAdapter<T>)
|
||||||
|
getTypeAdapter(constructorConstructor, gson, targetType, annotation, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate helper method to make sure callers create adapter in a consistent way
|
// Separate helper method to make sure callers create adapter in a consistent way
|
||||||
private static Object createAdapter(ConstructorConstructor constructorConstructor, Class<?> adapterClass) {
|
private static Object createAdapter(
|
||||||
// TODO: The exception messages created by ConstructorConstructor are currently written in the context of
|
ConstructorConstructor constructorConstructor, Class<?> adapterClass) {
|
||||||
// deserialization and for example suggest usage of TypeAdapter, which would not work for @JsonAdapter usage
|
// 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();
|
return constructorConstructor.get(TypeToken.get(adapterClass)).construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +99,12 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
|
||||||
return existingFactory != null ? existingFactory : factory;
|
return existingFactory != null ? existingFactory : factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
|
TypeAdapter<?> getTypeAdapter(
|
||||||
TypeToken<?> type, JsonAdapter annotation, boolean isClassAnnotation) {
|
ConstructorConstructor constructorConstructor,
|
||||||
|
Gson gson,
|
||||||
|
TypeToken<?> type,
|
||||||
|
JsonAdapter annotation,
|
||||||
|
boolean isClassAnnotation) {
|
||||||
Object instance = createAdapter(constructorConstructor, annotation.value());
|
Object instance = createAdapter(constructorConstructor, annotation.value());
|
||||||
|
|
||||||
TypeAdapter<?> typeAdapter;
|
TypeAdapter<?> typeAdapter;
|
||||||
|
@ -115,32 +120,35 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
|
||||||
|
|
||||||
typeAdapter = factory.create(gson, type);
|
typeAdapter = factory.create(gson, type);
|
||||||
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
|
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
|
||||||
JsonSerializer<?> serializer = instance instanceof JsonSerializer
|
JsonSerializer<?> serializer =
|
||||||
? (JsonSerializer<?>) instance
|
instance instanceof JsonSerializer ? (JsonSerializer<?>) instance : null;
|
||||||
: null;
|
JsonDeserializer<?> deserializer =
|
||||||
JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer
|
instance instanceof JsonDeserializer ? (JsonDeserializer<?>) instance : null;
|
||||||
? (JsonDeserializer<?>) instance
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for `Gson.getDelegateAdapter`
|
// Uses dummy factory instances because TreeTypeAdapter needs a 'skipPast' factory for
|
||||||
// call and has to differentiate there whether TreeTypeAdapter was created for @JsonAdapter on class or field
|
// `Gson.getDelegateAdapter` call and has to differentiate there whether TreeTypeAdapter was
|
||||||
|
// created for @JsonAdapter on class or field
|
||||||
TypeAdapterFactory skipPast;
|
TypeAdapterFactory skipPast;
|
||||||
if (isClassAnnotation) {
|
if (isClassAnnotation) {
|
||||||
skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;
|
skipPast = TREE_TYPE_CLASS_DUMMY_FACTORY;
|
||||||
} else {
|
} else {
|
||||||
skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;
|
skipPast = TREE_TYPE_FIELD_DUMMY_FACTORY;
|
||||||
}
|
}
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);
|
TypeAdapter<?> tempAdapter =
|
||||||
|
new TreeTypeAdapter(serializer, deserializer, gson, type, skipPast, nullSafe);
|
||||||
typeAdapter = tempAdapter;
|
typeAdapter = tempAdapter;
|
||||||
|
|
||||||
// TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()`
|
// TreeTypeAdapter handles nullSafe; don't additionally call `nullSafe()`
|
||||||
nullSafe = false;
|
nullSafe = false;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Invalid attempt to bind an instance of "
|
throw new IllegalArgumentException(
|
||||||
+ instance.getClass().getName() + " as a @JsonAdapter for " + type.toString()
|
"Invalid attempt to bind an instance of "
|
||||||
+ ". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory,"
|
+ instance.getClass().getName()
|
||||||
+ " JsonSerializer or JsonDeserializer.");
|
+ " as a @JsonAdapter for "
|
||||||
|
+ type.toString()
|
||||||
|
+ ". @JsonAdapter value must be a TypeAdapter, TypeAdapterFactory,"
|
||||||
|
+ " JsonSerializer or JsonDeserializer.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeAdapter != null && nullSafe) {
|
if (typeAdapter != null && nullSafe) {
|
||||||
|
@ -173,8 +181,8 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
|
||||||
|
|
||||||
// If no factory has been created for the type yet check manually for a @JsonAdapter annotation
|
// If no factory has been created for the type yet check manually for a @JsonAdapter annotation
|
||||||
// which specifies a TypeAdapterFactory
|
// which specifies a TypeAdapterFactory
|
||||||
// Otherwise behavior would not be consistent, depending on whether or not adapter had been requested
|
// Otherwise behavior would not be consistent, depending on whether or not adapter had been
|
||||||
// before call to `isClassJsonAdapterFactory` was made
|
// requested before call to `isClassJsonAdapterFactory` was made
|
||||||
JsonAdapter annotation = getAnnotation(rawType);
|
JsonAdapter annotation = getAnnotation(rawType);
|
||||||
if (annotation == null) {
|
if (annotation == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -41,80 +41,91 @@ import java.util.Map;
|
||||||
* Adapts maps to either JSON objects or JSON arrays.
|
* Adapts maps to either JSON objects or JSON arrays.
|
||||||
*
|
*
|
||||||
* <h2>Maps as JSON objects</h2>
|
* <h2>Maps as JSON objects</h2>
|
||||||
* For primitive keys or when complex map key serialization is not enabled, this
|
*
|
||||||
* converts Java {@link Map Maps} to JSON Objects. This requires that map keys
|
* For primitive keys or when complex map key serialization is not enabled, this converts Java
|
||||||
* can be serialized as strings; this is insufficient for some key types. For
|
* {@link Map Maps} to JSON Objects. This requires that map keys can be serialized as strings; this
|
||||||
* example, consider a map whose keys are points on a grid. The default JSON
|
* is insufficient for some key types. For example, consider a map whose keys are points on a grid.
|
||||||
* form encodes reasonably: <pre> {@code
|
* The default JSON form encodes reasonably:
|
||||||
* Map<Point, String> original = new LinkedHashMap<>();
|
*
|
||||||
* original.put(new Point(5, 6), "a");
|
* <pre>{@code
|
||||||
* original.put(new Point(8, 8), "b");
|
* Map<Point, String> original = new LinkedHashMap<>();
|
||||||
* System.out.println(gson.toJson(original, type));
|
* original.put(new Point(5, 6), "a");
|
||||||
|
* original.put(new Point(8, 8), "b");
|
||||||
|
* System.out.println(gson.toJson(original, type));
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* The above code prints this JSON object:<pre> {@code
|
*
|
||||||
* {
|
* The above code prints this JSON object:
|
||||||
* "(5,6)": "a",
|
*
|
||||||
* "(8,8)": "b"
|
* <pre>{@code
|
||||||
* }
|
* {
|
||||||
|
* "(5,6)": "a",
|
||||||
|
* "(8,8)": "b"
|
||||||
|
* }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* But GSON is unable to deserialize this value because the JSON string name is
|
*
|
||||||
* just the {@link Object#toString() toString()} of the map key. Attempting to
|
* But GSON is unable to deserialize this value because the JSON string name is just the {@link
|
||||||
* convert the above JSON to an object fails with a parse exception:
|
* Object#toString() toString()} of the map key. Attempting to convert the above JSON to an object
|
||||||
|
* fails with a parse exception:
|
||||||
|
*
|
||||||
* <pre>com.google.gson.JsonParseException: Expecting object found: "(5,6)"
|
* <pre>com.google.gson.JsonParseException: Expecting object found: "(5,6)"
|
||||||
* at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
|
* at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
|
||||||
* at com.google.gson.ObjectNavigator.navigateClassFields
|
* at com.google.gson.ObjectNavigator.navigateClassFields
|
||||||
* ...</pre>
|
* ...</pre>
|
||||||
*
|
*
|
||||||
* <h2>Maps as JSON arrays</h2>
|
* <h2>Maps as JSON arrays</h2>
|
||||||
* An alternative approach taken by this type adapter when it is required and
|
*
|
||||||
* complex map key serialization is enabled is to encode maps as arrays of map
|
* An alternative approach taken by this type adapter when it is required and complex map key
|
||||||
* entries. Each map entry is a two element array containing a key and a value.
|
* serialization is enabled is to encode maps as arrays of map entries. Each map entry is a two
|
||||||
* This approach is more flexible because any type can be used as the map's key;
|
* element array containing a key and a value. This approach is more flexible because any type can
|
||||||
* not just strings. But it's also less portable because the receiver of such
|
* be used as the map's key; not just strings. But it's also less portable because the receiver of
|
||||||
* JSON must be aware of the map entry convention.
|
* such JSON must be aware of the map entry convention.
|
||||||
*
|
*
|
||||||
* <p>Register this adapter when you are creating your GSON instance.
|
* <p>Register this adapter when you are creating your GSON instance.
|
||||||
* <pre> {@code
|
*
|
||||||
* Gson gson = new GsonBuilder()
|
* <pre>{@code
|
||||||
* .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
|
* Gson gson = new GsonBuilder()
|
||||||
* .create();
|
* .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
|
||||||
|
* .create();
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* This will change the structure of the JSON emitted by the code above. Now we
|
*
|
||||||
* get an array. In this case the arrays elements are map entries:
|
* This will change the structure of the JSON emitted by the code above. Now we get an array. In
|
||||||
* <pre> {@code
|
* this case the arrays elements are map entries:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* [
|
||||||
* [
|
* [
|
||||||
* [
|
* {
|
||||||
* {
|
* "x": 5,
|
||||||
* "x": 5,
|
* "y": 6
|
||||||
* "y": 6
|
* },
|
||||||
* },
|
* "a",
|
||||||
* "a",
|
* ],
|
||||||
* ],
|
* [
|
||||||
* [
|
* {
|
||||||
* {
|
* "x": 8,
|
||||||
* "x": 8,
|
* "y": 8
|
||||||
* "y": 8
|
* },
|
||||||
* },
|
* "b"
|
||||||
* "b"
|
|
||||||
* ]
|
|
||||||
* ]
|
* ]
|
||||||
|
* ]
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* This format will serialize and deserialize just fine as long as this adapter
|
*
|
||||||
* is registered.
|
* This format will serialize and deserialize just fine as long as this adapter is registered.
|
||||||
*/
|
*/
|
||||||
public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final ConstructorConstructor constructorConstructor;
|
private final ConstructorConstructor constructorConstructor;
|
||||||
final boolean complexMapKeySerialization;
|
final boolean complexMapKeySerialization;
|
||||||
final boolean duplicateMapKeyDeserialization;
|
final boolean duplicateMapKeyDeserialization;
|
||||||
|
|
||||||
public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
|
public MapTypeAdapterFactory(
|
||||||
boolean complexMapKeySerialization, boolean duplicateMapKeyDeserialization) {
|
ConstructorConstructor constructorConstructor, boolean complexMapKeySerialization, boolean duplicateMapKeyDeserialization) {
|
||||||
this.constructorConstructor = constructorConstructor;
|
this.constructorConstructor = constructorConstructor;
|
||||||
this.complexMapKeySerialization = complexMapKeySerialization;
|
this.complexMapKeySerialization = complexMapKeySerialization;
|
||||||
this.duplicateMapKeyDeserialization = duplicateMapKeyDeserialization;
|
this.duplicateMapKeyDeserialization = duplicateMapKeyDeserialization;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
Type type = typeToken.getType();
|
Type type = typeToken.getType();
|
||||||
|
|
||||||
Class<? super T> rawType = typeToken.getRawType();
|
Class<? super T> rawType = typeToken.getRawType();
|
||||||
|
@ -129,14 +140,13 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
// we don't define a type parameter for the key or value types
|
// we don't define a type parameter for the key or value types
|
||||||
TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter,
|
TypeAdapter<T> result =
|
||||||
keyAndValueTypes[1], valueAdapter, constructor);
|
new Adapter(
|
||||||
|
gson, keyAndValueTypes[0], keyAdapter, keyAndValueTypes[1], valueAdapter, constructor);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns a type adapter that writes the value as a string. */
|
||||||
* Returns a type adapter that writes the value as a string.
|
|
||||||
*/
|
|
||||||
private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
|
private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
|
||||||
return (keyType == boolean.class || keyType == Boolean.class)
|
return (keyType == boolean.class || keyType == Boolean.class)
|
||||||
? TypeAdapters.BOOLEAN_AS_STRING
|
? TypeAdapters.BOOLEAN_AS_STRING
|
||||||
|
@ -148,17 +158,21 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final TypeAdapter<V> valueTypeAdapter;
|
private final TypeAdapter<V> valueTypeAdapter;
|
||||||
private final ObjectConstructor<? extends Map<K, V>> constructor;
|
private final ObjectConstructor<? extends Map<K, V>> constructor;
|
||||||
|
|
||||||
public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter,
|
public Adapter(
|
||||||
Type valueType, TypeAdapter<V> valueTypeAdapter,
|
Gson context,
|
||||||
|
Type keyType,
|
||||||
|
TypeAdapter<K> keyTypeAdapter,
|
||||||
|
Type valueType,
|
||||||
|
TypeAdapter<V> valueTypeAdapter,
|
||||||
ObjectConstructor<? extends Map<K, V>> constructor) {
|
ObjectConstructor<? extends Map<K, V>> constructor) {
|
||||||
this.keyTypeAdapter =
|
this.keyTypeAdapter = new TypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType);
|
||||||
new TypeAdapterRuntimeTypeWrapper<>(context, keyTypeAdapter, keyType);
|
|
||||||
this.valueTypeAdapter =
|
this.valueTypeAdapter =
|
||||||
new TypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType);
|
new TypeAdapterRuntimeTypeWrapper<>(context, valueTypeAdapter, valueType);
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Map<K, V> read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Map<K, V> read(JsonReader in) throws IOException {
|
||||||
JsonToken peek = in.peek();
|
JsonToken peek = in.peek();
|
||||||
if (peek == JsonToken.NULL) {
|
if (peek == JsonToken.NULL) {
|
||||||
in.nextNull();
|
in.nextNull();
|
||||||
|
@ -196,7 +210,8 @@ public final class MapTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Map<K, V> map) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, Map<K, V> map) throws IOException {
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
out.nullValue();
|
out.nullValue();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -18,25 +18,21 @@ package com.google.gson.internal.bind;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.ToNumberStrategy;
|
|
||||||
import com.google.gson.ToNumberPolicy;
|
import com.google.gson.ToNumberPolicy;
|
||||||
|
import com.google.gson.ToNumberStrategy;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
import com.google.gson.TypeAdapterFactory;
|
import com.google.gson.TypeAdapterFactory;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/** Type adapter for {@link Number}. */
|
||||||
* Type adapter for {@link Number}.
|
|
||||||
*/
|
|
||||||
public final class NumberTypeAdapter extends TypeAdapter<Number> {
|
public final class NumberTypeAdapter extends TypeAdapter<Number> {
|
||||||
/**
|
/** Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}. */
|
||||||
* Gson default factory using {@link ToNumberPolicy#LAZILY_PARSED_NUMBER}.
|
private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY =
|
||||||
*/
|
newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER);
|
||||||
private static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = newFactory(ToNumberPolicy.LAZILY_PARSED_NUMBER);
|
|
||||||
|
|
||||||
private final ToNumberStrategy toNumberStrategy;
|
private final ToNumberStrategy toNumberStrategy;
|
||||||
|
|
||||||
|
@ -48,7 +44,8 @@ public final class NumberTypeAdapter extends TypeAdapter<Number> {
|
||||||
final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy);
|
final NumberTypeAdapter adapter = new NumberTypeAdapter(toNumberStrategy);
|
||||||
return new TypeAdapterFactory() {
|
return new TypeAdapterFactory() {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
return type.getRawType() == Number.class ? (TypeAdapter<T>) adapter : null;
|
return type.getRawType() == Number.class ? (TypeAdapter<T>) adapter : null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -62,21 +59,24 @@ public final class NumberTypeAdapter extends TypeAdapter<Number> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Number read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Number read(JsonReader in) throws IOException {
|
||||||
JsonToken jsonToken = in.peek();
|
JsonToken jsonToken = in.peek();
|
||||||
switch (jsonToken) {
|
switch (jsonToken) {
|
||||||
case NULL:
|
case NULL:
|
||||||
in.nextNull();
|
in.nextNull();
|
||||||
return null;
|
return null;
|
||||||
case NUMBER:
|
case NUMBER:
|
||||||
case STRING:
|
case STRING:
|
||||||
return toNumberStrategy.readNumber(in);
|
return toNumberStrategy.readNumber(in);
|
||||||
default:
|
default:
|
||||||
throw new JsonSyntaxException("Expecting number, got: " + jsonToken + "; at path " + in.getPath());
|
throw new JsonSyntaxException(
|
||||||
|
"Expecting number, got: " + jsonToken + "; at path " + in.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Number value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, Number value) throws IOException {
|
||||||
out.value(value);
|
out.value(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,11 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts types whose static type is only 'Object'. Uses getClass() on
|
* Adapts types whose static type is only 'Object'. Uses getClass() on serialization and a
|
||||||
* serialization and a primitive/Map/List on deserialization.
|
* primitive/Map/List on deserialization.
|
||||||
*/
|
*/
|
||||||
public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||||
/**
|
/** Gson default factory using {@link ToNumberPolicy#DOUBLE}. */
|
||||||
* Gson default factory using {@link ToNumberPolicy#DOUBLE}.
|
|
||||||
*/
|
|
||||||
private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE);
|
private static final TypeAdapterFactory DOUBLE_FACTORY = newFactory(ToNumberPolicy.DOUBLE);
|
||||||
|
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
@ -54,7 +52,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||||
private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) {
|
private static TypeAdapterFactory newFactory(final ToNumberStrategy toNumberStrategy) {
|
||||||
return new TypeAdapterFactory() {
|
return new TypeAdapterFactory() {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
if (type.getRawType() == Object.class) {
|
if (type.getRawType() == Object.class) {
|
||||||
return (TypeAdapter<T>) new ObjectTypeAdapter(gson, toNumberStrategy);
|
return (TypeAdapter<T>) new ObjectTypeAdapter(gson, toNumberStrategy);
|
||||||
}
|
}
|
||||||
|
@ -72,8 +71,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to begin reading a JSON array or JSON object, returning {@code null} if
|
* Tries to begin reading a JSON array or JSON object, returning {@code null} if the next element
|
||||||
* the next element is neither of those.
|
* is neither of those.
|
||||||
*/
|
*/
|
||||||
private Object tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
|
private Object tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
|
||||||
switch (peeked) {
|
switch (peeked) {
|
||||||
|
@ -106,7 +105,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Object read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Object read(JsonReader in) throws IOException {
|
||||||
// Either List or Map
|
// Either List or Map
|
||||||
Object current;
|
Object current;
|
||||||
JsonToken peeked = in.peek();
|
JsonToken peeked = in.peek();
|
||||||
|
@ -166,7 +166,8 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Object value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, Object value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
out.nullValue();
|
out.nullValue();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -56,9 +56,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/** Type adapter that reflects over the fields and methods of a class. */
|
||||||
* Type adapter that reflects over the fields and methods of a class.
|
|
||||||
*/
|
|
||||||
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final ConstructorConstructor constructorConstructor;
|
private final ConstructorConstructor constructorConstructor;
|
||||||
private final FieldNamingStrategy fieldNamingPolicy;
|
private final FieldNamingStrategy fieldNamingPolicy;
|
||||||
|
@ -66,8 +64,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
|
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
|
||||||
private final List<ReflectionAccessFilter> reflectionFilters;
|
private final List<ReflectionAccessFilter> reflectionFilters;
|
||||||
|
|
||||||
public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
|
public ReflectiveTypeAdapterFactory(
|
||||||
FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
|
ConstructorConstructor constructorConstructor,
|
||||||
|
FieldNamingStrategy fieldNamingPolicy,
|
||||||
|
Excluder excluder,
|
||||||
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
|
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
|
||||||
List<ReflectionAccessFilter> reflectionFilters) {
|
List<ReflectionAccessFilter> reflectionFilters) {
|
||||||
this.constructorConstructor = constructorConstructor;
|
this.constructorConstructor = constructorConstructor;
|
||||||
|
@ -78,7 +78,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean includeField(Field f, boolean serialize) {
|
private boolean includeField(Field f, boolean serialize) {
|
||||||
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
|
return !excluder.excludeField(f, serialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** first element holds the default name */
|
/** first element holds the default name */
|
||||||
|
@ -110,40 +110,77 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
return null; // it's a primitive!
|
return null; // it's a primitive!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't allow using reflection on anonymous and local classes because synthetic fields for
|
||||||
|
// captured enclosing values make this unreliable
|
||||||
|
if (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {
|
||||||
|
// This adapter just serializes and deserializes null, ignoring the actual values
|
||||||
|
// This is done for backward compatibility; troubleshooting-wise it might be better to throw
|
||||||
|
// exceptions
|
||||||
|
return new TypeAdapter<T>() {
|
||||||
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
|
in.skipValue();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
out.nullValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AnonymousOrNonStaticLocalClassAdapter";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
FilterResult filterResult =
|
FilterResult filterResult =
|
||||||
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
||||||
if (filterResult == FilterResult.BLOCK_ALL) {
|
if (filterResult == FilterResult.BLOCK_ALL) {
|
||||||
throw new JsonIOException(
|
throw new JsonIOException(
|
||||||
"ReflectionAccessFilter does not permit using reflection for " + raw
|
"ReflectionAccessFilter does not permit using reflection for "
|
||||||
+ ". Register a TypeAdapter for this type or adjust the access filter.");
|
+ raw
|
||||||
|
+ ". Register a TypeAdapter for this type or adjust the access filter.");
|
||||||
}
|
}
|
||||||
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
||||||
|
|
||||||
// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will always be false
|
// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
|
||||||
// on JVMs that do not support records.
|
// always be false on JVMs that do not support records.
|
||||||
if (ReflectionHelper.isRecord(raw)) {
|
if (ReflectionHelper.isRecord(raw)) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
TypeAdapter<T> adapter = (TypeAdapter<T>) new RecordAdapter<>(raw,
|
TypeAdapter<T> adapter =
|
||||||
getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
|
(TypeAdapter<T>)
|
||||||
|
new RecordAdapter<>(
|
||||||
|
raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);
|
||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectConstructor<T> constructor = constructorConstructor.get(type);
|
ObjectConstructor<T> constructor = constructorConstructor.get(type);
|
||||||
return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
|
return new FieldReflectionAdapter<>(
|
||||||
|
constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <M extends AccessibleObject & Member> void checkAccessible(Object object, M member) {
|
private static <M extends AccessibleObject & Member> void checkAccessible(
|
||||||
if (!ReflectionAccessFilterHelper.canAccess(member, Modifier.isStatic(member.getModifiers()) ? null : object)) {
|
Object object, M member) {
|
||||||
|
if (!ReflectionAccessFilterHelper.canAccess(
|
||||||
|
member, Modifier.isStatic(member.getModifiers()) ? null : object)) {
|
||||||
String memberDescription = ReflectionHelper.getAccessibleObjectDescription(member, true);
|
String memberDescription = ReflectionHelper.getAccessibleObjectDescription(member, true);
|
||||||
throw new JsonIOException(memberDescription + " is not accessible and ReflectionAccessFilter does not"
|
throw new JsonIOException(
|
||||||
+ " permit making it accessible. Register a TypeAdapter for the declaring type, adjust the"
|
memberDescription
|
||||||
+ " access filter or increase the visibility of the element and its declaring type.");
|
+ " is not accessible and ReflectionAccessFilter does not permit making it"
|
||||||
|
+ " accessible. Register a TypeAdapter for the declaring type, adjust the access"
|
||||||
|
+ " filter or increase the visibility of the element and its declaring type.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BoundField createBoundField(
|
private BoundField createBoundField(
|
||||||
final Gson context, final Field field, final Method accessor, final String name,
|
final Gson context,
|
||||||
final TypeToken<?> fieldType, boolean serialize, boolean deserialize,
|
final Field field,
|
||||||
|
final Method accessor,
|
||||||
|
final String serializedName,
|
||||||
|
final TypeToken<?> fieldType,
|
||||||
|
final boolean serialize,
|
||||||
final boolean blockInaccessible) {
|
final boolean blockInaccessible) {
|
||||||
|
|
||||||
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
|
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
|
||||||
|
@ -155,26 +192,30 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
TypeAdapter<?> mapped = null;
|
TypeAdapter<?> mapped = null;
|
||||||
if (annotation != null) {
|
if (annotation != null) {
|
||||||
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter
|
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter
|
||||||
mapped = jsonAdapterFactory.getTypeAdapter(
|
mapped =
|
||||||
constructorConstructor, context, fieldType, annotation, false);
|
jsonAdapterFactory.getTypeAdapter(
|
||||||
|
constructorConstructor, context, fieldType, annotation, false);
|
||||||
}
|
}
|
||||||
final boolean jsonAdapterPresent = mapped != null;
|
final boolean jsonAdapterPresent = mapped != null;
|
||||||
if (mapped == null) mapped = context.getAdapter(fieldType);
|
if (mapped == null) {
|
||||||
|
mapped = context.getAdapter(fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
|
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
|
||||||
final TypeAdapter<Object> writeTypeAdapter;
|
final TypeAdapter<Object> writeTypeAdapter;
|
||||||
if (serialize) {
|
if (serialize) {
|
||||||
writeTypeAdapter = jsonAdapterPresent ? typeAdapter
|
writeTypeAdapter =
|
||||||
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
|
jsonAdapterPresent
|
||||||
|
? typeAdapter
|
||||||
|
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
|
||||||
} else {
|
} else {
|
||||||
// Will never actually be used, but we set it to avoid confusing nullness-analysis tools
|
// Will never actually be used, but we set it to avoid confusing nullness-analysis tools
|
||||||
writeTypeAdapter = typeAdapter;
|
writeTypeAdapter = typeAdapter;
|
||||||
}
|
}
|
||||||
return new BoundField(name, field, serialize, deserialize) {
|
return new BoundField(serializedName, field) {
|
||||||
@Override void write(JsonWriter writer, Object source)
|
@Override
|
||||||
throws IOException, IllegalAccessException {
|
void write(JsonWriter writer, Object source) throws IOException, IllegalAccessException {
|
||||||
if (!serialized) return;
|
|
||||||
if (blockInaccessible) {
|
if (blockInaccessible) {
|
||||||
if (accessor == null) {
|
if (accessor == null) {
|
||||||
checkAccessible(source, field);
|
checkAccessible(source, field);
|
||||||
|
@ -190,8 +231,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
try {
|
try {
|
||||||
fieldValue = accessor.invoke(source);
|
fieldValue = accessor.invoke(source);
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
String accessorDescription = ReflectionHelper.getAccessibleObjectDescription(accessor, false);
|
String accessorDescription =
|
||||||
throw new JsonIOException("Accessor " + accessorDescription + " threw exception", e.getCause());
|
ReflectionHelper.getAccessibleObjectDescription(accessor, false);
|
||||||
|
throw new JsonIOException(
|
||||||
|
"Accessor " + accessorDescription + " threw exception", e.getCause());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fieldValue = field.get(source);
|
fieldValue = field.get(source);
|
||||||
|
@ -200,16 +243,20 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
// avoid direct recursion
|
// avoid direct recursion
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writer.name(name);
|
writer.name(serializedName);
|
||||||
writeTypeAdapter.write(writer, fieldValue);
|
writeTypeAdapter.write(writer, fieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void readIntoArray(JsonReader reader, int index, Object[] target) throws IOException, JsonParseException {
|
void readIntoArray(JsonReader reader, int index, Object[] target)
|
||||||
|
throws IOException, JsonParseException {
|
||||||
Object fieldValue = typeAdapter.read(reader);
|
Object fieldValue = typeAdapter.read(reader);
|
||||||
if (fieldValue == null && isPrimitive) {
|
if (fieldValue == null && isPrimitive) {
|
||||||
throw new JsonParseException("null is not allowed as value for record component '" + fieldName + "'"
|
throw new JsonParseException(
|
||||||
+ " of primitive type; at path " + reader.getPath());
|
"null is not allowed as value for record component '"
|
||||||
|
+ fieldName
|
||||||
|
+ "' of primitive type; at path "
|
||||||
|
+ reader.getPath());
|
||||||
}
|
}
|
||||||
target[index] = fieldValue;
|
target[index] = fieldValue;
|
||||||
}
|
}
|
||||||
|
@ -222,7 +269,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
if (blockInaccessible) {
|
if (blockInaccessible) {
|
||||||
checkAccessible(target, field);
|
checkAccessible(target, field);
|
||||||
} else if (isStaticFinalField) {
|
} else if (isStaticFinalField) {
|
||||||
// Reflection does not permit setting value of `static final` field, even after calling `setAccessible`
|
// Reflection does not permit setting value of `static final` field, even after calling
|
||||||
|
// `setAccessible`
|
||||||
// Handle this here to avoid causing IllegalAccessException when calling `Field.set`
|
// Handle this here to avoid causing IllegalAccessException when calling `Field.set`
|
||||||
String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);
|
String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);
|
||||||
throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);
|
throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);
|
||||||
|
@ -233,12 +281,48 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw,
|
private static class FieldsData {
|
||||||
boolean blockInaccessible, boolean isRecord) {
|
public static final FieldsData EMPTY =
|
||||||
Map<String, BoundField> result = new LinkedHashMap<>();
|
new FieldsData(
|
||||||
if (raw.isInterface()) {
|
Collections.<String, BoundField>emptyMap(), Collections.<BoundField>emptyList());
|
||||||
return result;
|
|
||||||
|
/** Maps from JSON member name to field */
|
||||||
|
public final Map<String, BoundField> deserializedFields;
|
||||||
|
|
||||||
|
public final List<BoundField> serializedFields;
|
||||||
|
|
||||||
|
public FieldsData(
|
||||||
|
Map<String, BoundField> deserializedFields, List<BoundField> serializedFields) {
|
||||||
|
this.deserializedFields = deserializedFields;
|
||||||
|
this.serializedFields = serializedFields;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IllegalArgumentException createDuplicateFieldException(
|
||||||
|
Class<?> declaringType, String duplicateName, Field field1, Field field2) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Class "
|
||||||
|
+ declaringType.getName()
|
||||||
|
+ " declares multiple JSON fields named '"
|
||||||
|
+ duplicateName
|
||||||
|
+ "'; conflict is caused by fields "
|
||||||
|
+ ReflectionHelper.fieldToString(field1)
|
||||||
|
+ " and "
|
||||||
|
+ ReflectionHelper.fieldToString(field2)
|
||||||
|
+ "\nSee "
|
||||||
|
+ TroubleshootingGuide.createUrl("duplicate-fields"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FieldsData getBoundFields(
|
||||||
|
Gson context, TypeToken<?> type, Class<?> raw, boolean blockInaccessible, boolean isRecord) {
|
||||||
|
if (raw.isInterface()) {
|
||||||
|
return FieldsData.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, BoundField> deserializedFields = new LinkedHashMap<>();
|
||||||
|
// For serialized fields use a Map to track duplicate field names; otherwise this could be a
|
||||||
|
// List<BoundField> instead
|
||||||
|
Map<String, BoundField> serializedFields = new LinkedHashMap<>();
|
||||||
|
|
||||||
Class<?> originalRaw = raw;
|
Class<?> originalRaw = raw;
|
||||||
while (raw != Object.class) {
|
while (raw != Object.class) {
|
||||||
|
@ -246,11 +330,15 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
// For inherited fields, check if access to their declaring class is allowed
|
// For inherited fields, check if access to their declaring class is allowed
|
||||||
if (raw != originalRaw && fields.length > 0) {
|
if (raw != originalRaw && fields.length > 0) {
|
||||||
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
FilterResult filterResult =
|
||||||
|
ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
||||||
if (filterResult == FilterResult.BLOCK_ALL) {
|
if (filterResult == FilterResult.BLOCK_ALL) {
|
||||||
throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for " + raw
|
throw new JsonIOException(
|
||||||
+ " (supertype of " + originalRaw + "). Register a TypeAdapter for this type"
|
"ReflectionAccessFilter does not permit using reflection for "
|
||||||
+ " or adjust the access filter.");
|
+ raw
|
||||||
|
+ " (supertype of "
|
||||||
|
+ originalRaw
|
||||||
|
+ "). Register a TypeAdapter for this type or adjust the access filter.");
|
||||||
}
|
}
|
||||||
blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
||||||
}
|
}
|
||||||
|
@ -261,13 +349,16 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
if (!serialize && !deserialize) {
|
if (!serialize && !deserialize) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// The accessor method is only used for records. If the type is a record, we will read out values
|
// The accessor method is only used for records. If the type is a record, we will read out
|
||||||
// via its accessor method instead of via reflection. This way we will bypass the accessible restrictions
|
// values via its accessor method instead of via reflection. This way we will bypass the
|
||||||
|
// accessible restrictions
|
||||||
Method accessor = null;
|
Method accessor = null;
|
||||||
if (isRecord) {
|
if (isRecord) {
|
||||||
// If there is a static field on a record, there will not be an accessor. Instead we will use the default
|
// If there is a static field on a record, there will not be an accessor. Instead we will
|
||||||
// field serialization logic, but for deserialization the field is excluded for simplicity. Note that Gson
|
// use the default field serialization logic, but for deserialization the field is
|
||||||
// ignores static fields by default, but GsonBuilder.excludeFieldsWithModifiers can overwrite this.
|
// excluded for simplicity.
|
||||||
|
// Note that Gson ignores static fields by default, but
|
||||||
|
// GsonBuilder.excludeFieldsWithModifiers can overwrite this.
|
||||||
if (Modifier.isStatic(field.getModifiers())) {
|
if (Modifier.isStatic(field.getModifiers())) {
|
||||||
deserialize = false;
|
deserialize = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -278,12 +369,15 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @SerializedName can be placed on accessor method, but it is not supported there
|
// @SerializedName can be placed on accessor method, but it is not supported there
|
||||||
// If field and method have annotation it is not easily possible to determine if accessor method
|
// If field and method have annotation it is not easily possible to determine if
|
||||||
// is implicit and has inherited annotation, or if it is explicitly declared with custom annotation
|
// accessor method is implicit and has inherited annotation, or if it is explicitly
|
||||||
|
// declared with custom annotation
|
||||||
if (accessor.getAnnotation(SerializedName.class) != null
|
if (accessor.getAnnotation(SerializedName.class) != null
|
||||||
&& field.getAnnotation(SerializedName.class) == null) {
|
&& field.getAnnotation(SerializedName.class) == null) {
|
||||||
String methodDescription = ReflectionHelper.getAccessibleObjectDescription(accessor, false);
|
String methodDescription =
|
||||||
throw new JsonIOException("@SerializedName on " + methodDescription + " is not supported");
|
ReflectionHelper.getAccessibleObjectDescription(accessor, false);
|
||||||
|
throw new JsonIOException(
|
||||||
|
"@SerializedName on " + methodDescription + " is not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,54 +387,72 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
if (!blockInaccessible && accessor == null) {
|
if (!blockInaccessible && accessor == null) {
|
||||||
ReflectionHelper.makeAccessible(field);
|
ReflectionHelper.makeAccessible(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
|
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
|
||||||
List<String> fieldNames = getFieldNames(field);
|
List<String> fieldNames = getFieldNames(field);
|
||||||
BoundField previous = null;
|
String serializedName = fieldNames.get(0);
|
||||||
for (int i = 0, size = fieldNames.size(); i < size; ++i) {
|
BoundField boundField =
|
||||||
String name = fieldNames.get(i);
|
createBoundField(
|
||||||
if (i != 0) serialize = false; // only serialize the default name
|
context,
|
||||||
BoundField boundField = createBoundField(context, field, accessor, name,
|
field,
|
||||||
TypeToken.get(fieldType), serialize, deserialize, blockInaccessible);
|
accessor,
|
||||||
BoundField replaced = result.put(name, boundField);
|
serializedName,
|
||||||
if (previous == null) previous = replaced;
|
TypeToken.get(fieldType),
|
||||||
|
serialize,
|
||||||
|
blockInaccessible);
|
||||||
|
|
||||||
|
if (deserialize) {
|
||||||
|
for (String name : fieldNames) {
|
||||||
|
BoundField replaced = deserializedFields.put(name, boundField);
|
||||||
|
|
||||||
|
if (replaced != null) {
|
||||||
|
throw createDuplicateFieldException(originalRaw, name, replaced.field, field);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (previous != null) {
|
|
||||||
throw new IllegalArgumentException("Class " + originalRaw.getName()
|
if (serialize) {
|
||||||
+ " declares multiple JSON fields named '" + previous.name + "'; conflict is caused"
|
BoundField replaced = serializedFields.put(serializedName, boundField);
|
||||||
+ " by fields " + ReflectionHelper.fieldToString(previous.field) + " and " + ReflectionHelper.fieldToString(field)
|
if (replaced != null) {
|
||||||
+ "\nSee " + TroubleshootingGuide.createUrl("duplicate-fields"));
|
throw createDuplicateFieldException(originalRaw, serializedName, replaced.field, field);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
|
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
|
||||||
raw = type.getRawType();
|
raw = type.getRawType();
|
||||||
}
|
}
|
||||||
return result;
|
return new FieldsData(deserializedFields, new ArrayList<>(serializedFields.values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static abstract class BoundField {
|
abstract static class BoundField {
|
||||||
final String name;
|
/** Name used for serialization (but not for deserialization) */
|
||||||
|
final String serializedName;
|
||||||
|
|
||||||
final Field field;
|
final Field field;
|
||||||
|
|
||||||
/** Name of the underlying field */
|
/** Name of the underlying field */
|
||||||
final String fieldName;
|
final String fieldName;
|
||||||
final boolean serialized;
|
|
||||||
final boolean deserialized;
|
|
||||||
|
|
||||||
protected BoundField(String name, Field field, boolean serialized, boolean deserialized) {
|
protected BoundField(String serializedName, Field field) {
|
||||||
this.name = name;
|
this.serializedName = serializedName;
|
||||||
this.field = field;
|
this.field = field;
|
||||||
this.fieldName = field.getName();
|
this.fieldName = field.getName();
|
||||||
this.serialized = serialized;
|
|
||||||
this.deserialized = deserialized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read this field value from the source, and append its JSON value to the writer */
|
/** Read this field value from the source, and append its JSON value to the writer */
|
||||||
abstract void write(JsonWriter writer, Object source) throws IOException, IllegalAccessException;
|
abstract void write(JsonWriter writer, Object source)
|
||||||
|
throws IOException, IllegalAccessException;
|
||||||
|
|
||||||
/** Read the value into the target array, used to provide constructor arguments for records */
|
/** Read the value into the target array, used to provide constructor arguments for records */
|
||||||
abstract void readIntoArray(JsonReader reader, int index, Object[] target) throws IOException, JsonParseException;
|
abstract void readIntoArray(JsonReader reader, int index, Object[] target)
|
||||||
|
throws IOException, JsonParseException;
|
||||||
|
|
||||||
/** Read the value from the reader, and set it on the corresponding field on target via reflection */
|
/**
|
||||||
abstract void readIntoField(JsonReader reader, Object target) throws IOException, IllegalAccessException;
|
* Read the value from the reader, and set it on the corresponding field on target via
|
||||||
|
* reflection
|
||||||
|
*/
|
||||||
|
abstract void readIntoField(JsonReader reader, Object target)
|
||||||
|
throws IOException, IllegalAccessException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -349,19 +461,20 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
* <p>The {@link RecordAdapter} is a special case to handle records for JVMs that support it, for
|
* <p>The {@link RecordAdapter} is a special case to handle records for JVMs that support it, for
|
||||||
* all other types we use the {@link FieldReflectionAdapter}. This class encapsulates the common
|
* all other types we use the {@link FieldReflectionAdapter}. This class encapsulates the common
|
||||||
* logic for serialization and deserialization. During deserialization, we construct an
|
* logic for serialization and deserialization. During deserialization, we construct an
|
||||||
* accumulator A, which we use to accumulate values from the source JSON. After the object has been read in
|
* accumulator A, which we use to accumulate values from the source JSON. After the object has
|
||||||
* full, the {@link #finalize(Object)} method is used to convert the accumulator to an instance
|
* been read in full, the {@link #finalize(Object)} method is used to convert the accumulator to
|
||||||
* of T.
|
* an instance of T.
|
||||||
*
|
*
|
||||||
* @param <T> type of objects that this Adapter creates.
|
* @param <T> type of objects that this Adapter creates.
|
||||||
* @param <A> type of accumulator used to build the deserialization result.
|
* @param <A> type of accumulator used to build the deserialization result.
|
||||||
*/
|
*/
|
||||||
// This class is public because external projects check for this class with `instanceof` (even though it is internal)
|
// This class is public because external projects check for this class with `instanceof` (even
|
||||||
public static abstract class Adapter<T, A> extends TypeAdapter<T> {
|
// though it is internal)
|
||||||
final Map<String, BoundField> boundFields;
|
public abstract static class Adapter<T, A> extends TypeAdapter<T> {
|
||||||
|
private final FieldsData fieldsData;
|
||||||
|
|
||||||
Adapter(Map<String, BoundField> boundFields) {
|
Adapter(FieldsData fieldsData) {
|
||||||
this.boundFields = boundFields;
|
this.fieldsData = fieldsData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -373,7 +486,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
out.beginObject();
|
out.beginObject();
|
||||||
try {
|
try {
|
||||||
for (BoundField boundField : boundFields.values()) {
|
for (BoundField boundField : fieldsData.serializedFields) {
|
||||||
boundField.write(out, value);
|
boundField.write(out, value);
|
||||||
}
|
}
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
|
@ -390,13 +503,14 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
A accumulator = createAccumulator();
|
A accumulator = createAccumulator();
|
||||||
|
Map<String, BoundField> deserializedFields = fieldsData.deserializedFields;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
String name = in.nextName();
|
String name = in.nextName();
|
||||||
BoundField field = boundFields.get(name);
|
BoundField field = deserializedFields.get(name);
|
||||||
if (field == null || !field.deserialized) {
|
if (field == null) {
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
} else {
|
} else {
|
||||||
readField(accumulator, in, field);
|
readField(accumulator, in, field);
|
||||||
|
@ -413,12 +527,14 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
|
|
||||||
/** Create the Object that will be used to collect each field value */
|
/** Create the Object that will be used to collect each field value */
|
||||||
abstract A createAccumulator();
|
abstract A createAccumulator();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a single BoundField into the accumulator. The JsonReader will be pointed at the
|
* Read a single BoundField into the accumulator. The JsonReader will be pointed at the start of
|
||||||
* start of the value for the BoundField to read from.
|
* the value for the BoundField to read from.
|
||||||
*/
|
*/
|
||||||
abstract void readField(A accumulator, JsonReader in, BoundField field)
|
abstract void readField(A accumulator, JsonReader in, BoundField field)
|
||||||
throws IllegalAccessException, IOException;
|
throws IllegalAccessException, IOException;
|
||||||
|
|
||||||
/** Convert the accumulator to a final instance of T. */
|
/** Convert the accumulator to a final instance of T. */
|
||||||
abstract T finalize(A accumulator);
|
abstract T finalize(A accumulator);
|
||||||
}
|
}
|
||||||
|
@ -426,8 +542,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
private static final class FieldReflectionAdapter<T> extends Adapter<T, T> {
|
private static final class FieldReflectionAdapter<T> extends Adapter<T, T> {
|
||||||
private final ObjectConstructor<T> constructor;
|
private final ObjectConstructor<T> constructor;
|
||||||
|
|
||||||
FieldReflectionAdapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
|
FieldReflectionAdapter(ObjectConstructor<T> constructor, FieldsData fieldsData) {
|
||||||
super(boundFields);
|
super(fieldsData);
|
||||||
this.constructor = constructor;
|
this.constructor = constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,8 +574,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
// Map from component names to index into the constructors arguments.
|
// Map from component names to index into the constructors arguments.
|
||||||
private final Map<String, Integer> componentIndices = new HashMap<>();
|
private final Map<String, Integer> componentIndices = new HashMap<>();
|
||||||
|
|
||||||
RecordAdapter(Class<T> raw, Map<String, BoundField> boundFields, boolean blockInaccessible) {
|
RecordAdapter(Class<T> raw, FieldsData fieldsData, boolean blockInaccessible) {
|
||||||
super(boundFields);
|
super(fieldsData);
|
||||||
constructor = ReflectionHelper.getCanonicalRecordConstructor(raw);
|
constructor = ReflectionHelper.getCanonicalRecordConstructor(raw);
|
||||||
|
|
||||||
if (blockInaccessible) {
|
if (blockInaccessible) {
|
||||||
|
@ -475,8 +591,9 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
}
|
}
|
||||||
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
Class<?>[] parameterTypes = constructor.getParameterTypes();
|
||||||
|
|
||||||
// We need to ensure that we are passing non-null values to primitive fields in the constructor. To do this,
|
// We need to ensure that we are passing non-null values to primitive fields in the
|
||||||
// we create an Object[] where all primitives are initialized to non-null values.
|
// constructor. To do this, we create an Object[] where all primitives are initialized to
|
||||||
|
// non-null values.
|
||||||
constructorArgsDefaults = new Object[parameterTypes.length];
|
constructorArgsDefaults = new Object[parameterTypes.length];
|
||||||
for (int i = 0; i < parameterTypes.length; i++) {
|
for (int i = 0; i < parameterTypes.length; i++) {
|
||||||
// This will correctly be null for non-primitive types:
|
// This will correctly be null for non-primitive types:
|
||||||
|
@ -508,12 +625,15 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
Integer componentIndex = componentIndices.get(field.fieldName);
|
Integer componentIndex = componentIndices.get(field.fieldName);
|
||||||
if (componentIndex == null) {
|
if (componentIndex == null) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Could not find the index in the constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
|
"Could not find the index in the constructor '"
|
||||||
+ " for field with name '" + field.fieldName + "',"
|
+ ReflectionHelper.constructorToString(constructor)
|
||||||
+ " unable to determine which argument in the constructor the field corresponds"
|
+ "' for field with name '"
|
||||||
|
+ field.fieldName
|
||||||
|
+ "', unable to determine which argument in the constructor the field corresponds"
|
||||||
+ " to. This is unexpected behavior, as we expect the RecordComponents to have the"
|
+ " to. This is unexpected behavior, as we expect the RecordComponents to have the"
|
||||||
+ " same names as the fields in the Java class, and that the order of the"
|
+ " same names as the fields in the Java class, and that the order of the"
|
||||||
+ " RecordComponents is the same as the order of the canonical constructor parameters.");
|
+ " RecordComponents is the same as the order of the canonical constructor"
|
||||||
|
+ " parameters.");
|
||||||
}
|
}
|
||||||
field.readIntoArray(in, componentIndex, accumulator);
|
field.readIntoArray(in, componentIndex, accumulator);
|
||||||
}
|
}
|
||||||
|
@ -526,17 +646,23 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
||||||
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
||||||
}
|
}
|
||||||
// Note: InstantiationException should be impossible because record class is not abstract;
|
// Note: InstantiationException should be impossible because record class is not abstract;
|
||||||
// IllegalArgumentException should not be possible unless a bad adapter returns objects of the wrong type
|
// IllegalArgumentException should not be possible unless a bad adapter returns objects of
|
||||||
|
// the wrong type
|
||||||
catch (InstantiationException | IllegalArgumentException e) {
|
catch (InstantiationException | IllegalArgumentException e) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
|
"Failed to invoke constructor '"
|
||||||
+ " with args " + Arrays.toString(accumulator), e);
|
+ ReflectionHelper.constructorToString(constructor)
|
||||||
}
|
+ "' with args "
|
||||||
catch (InvocationTargetException e) {
|
+ Arrays.toString(accumulator),
|
||||||
|
e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
// TODO: JsonParseException ?
|
// TODO: JsonParseException ?
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
|
"Failed to invoke constructor '"
|
||||||
+ " with args " + Arrays.toString(accumulator), e.getCause());
|
+ ReflectionHelper.constructorToString(constructor)
|
||||||
|
+ "' with args "
|
||||||
|
+ Arrays.toString(accumulator),
|
||||||
|
e.getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,11 @@ package com.google.gson.internal.bind;
|
||||||
|
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
|
|
||||||
/**
|
/** Type adapter which might delegate serialization to another adapter. */
|
||||||
* Type adapter which might delegate serialization to another adapter.
|
|
||||||
*/
|
|
||||||
public abstract class SerializationDelegatingTypeAdapter<T> extends TypeAdapter<T> {
|
public abstract class SerializationDelegatingTypeAdapter<T> extends TypeAdapter<T> {
|
||||||
/**
|
/**
|
||||||
* Returns the adapter used for serialization, might be {@code this} or another adapter.
|
* Returns the adapter used for serialization, might be {@code this} or another adapter. That
|
||||||
* That other adapter might itself also be a {@code SerializationDelegatingTypeAdapter}.
|
* other adapter might itself also be a {@code SerializationDelegatingTypeAdapter}.
|
||||||
*/
|
*/
|
||||||
public abstract TypeAdapter<T> getSerializationDelegate();
|
public abstract TypeAdapter<T> getSerializationDelegate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,31 +34,38 @@ import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the
|
* Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the tree adapter may be
|
||||||
* tree adapter may be serialization-only or deserialization-only, this class
|
* serialization-only or deserialization-only, this class has a facility to look up a delegate type
|
||||||
* has a facility to lookup a delegate type adapter on demand.
|
* adapter on demand.
|
||||||
*/
|
*/
|
||||||
public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> {
|
public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> {
|
||||||
private final JsonSerializer<T> serializer;
|
private final JsonSerializer<T> serializer;
|
||||||
private final JsonDeserializer<T> deserializer;
|
private final JsonDeserializer<T> deserializer;
|
||||||
final Gson gson;
|
final Gson gson;
|
||||||
private final TypeToken<T> typeToken;
|
private final TypeToken<T> typeToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only intended as {@code skipPast} for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)},
|
* Only intended as {@code skipPast} for {@link Gson#getDelegateAdapter(TypeAdapterFactory,
|
||||||
* must not be used in any other way.
|
* TypeToken)}, must not be used in any other way.
|
||||||
*/
|
*/
|
||||||
private final TypeAdapterFactory skipPastForGetDelegateAdapter;
|
private final TypeAdapterFactory skipPastForGetDelegateAdapter;
|
||||||
|
|
||||||
private final GsonContextImpl context = new GsonContextImpl();
|
private final GsonContextImpl context = new GsonContextImpl();
|
||||||
private final boolean nullSafe;
|
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
|
||||||
* Field has to be {@code volatile} because {@link Gson} guarantees to be thread-safe.
|
* has to be {@code volatile} because {@link Gson} guarantees to be thread-safe.
|
||||||
*/
|
*/
|
||||||
private volatile TypeAdapter<T> delegate;
|
private volatile TypeAdapter<T> delegate;
|
||||||
|
|
||||||
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
|
public TreeTypeAdapter(
|
||||||
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast, boolean nullSafe) {
|
JsonSerializer<T> serializer,
|
||||||
|
JsonDeserializer<T> deserializer,
|
||||||
|
Gson gson,
|
||||||
|
TypeToken<T> typeToken,
|
||||||
|
TypeAdapterFactory skipPast,
|
||||||
|
boolean nullSafe) {
|
||||||
this.serializer = serializer;
|
this.serializer = serializer;
|
||||||
this.deserializer = deserializer;
|
this.deserializer = deserializer;
|
||||||
this.gson = gson;
|
this.gson = gson;
|
||||||
|
@ -67,12 +74,17 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
this.nullSafe = nullSafe;
|
this.nullSafe = nullSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
|
public TreeTypeAdapter(
|
||||||
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) {
|
JsonSerializer<T> serializer,
|
||||||
|
JsonDeserializer<T> deserializer,
|
||||||
|
Gson gson,
|
||||||
|
TypeToken<T> typeToken,
|
||||||
|
TypeAdapterFactory skipPast) {
|
||||||
this(serializer, deserializer, gson, typeToken, skipPast, true);
|
this(serializer, deserializer, gson, typeToken, skipPast, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public T read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
if (deserializer == null) {
|
if (deserializer == null) {
|
||||||
return delegate().read(in);
|
return delegate().read(in);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +95,8 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
return deserializer.deserialize(value, typeToken.getType(), context);
|
return deserializer.deserialize(value, typeToken.getType(), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
if (serializer == null) {
|
if (serializer == null) {
|
||||||
delegate().write(out, value);
|
delegate().write(out, value);
|
||||||
return;
|
return;
|
||||||
|
@ -97,7 +110,8 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeAdapter<T> delegate() {
|
private TypeAdapter<T> delegate() {
|
||||||
// A race might lead to `delegate` being assigned by multiple threads but the last assignment will stick
|
// A race might lead to `delegate` being assigned by multiple threads but the last assignment
|
||||||
|
// will stick
|
||||||
TypeAdapter<T> d = delegate;
|
TypeAdapter<T> d = delegate;
|
||||||
return d != null
|
return d != null
|
||||||
? d
|
? d
|
||||||
|
@ -105,25 +119,20 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type adapter which is used for serialization. Returns {@code this}
|
* Returns the type adapter which is used for serialization. Returns {@code this} if this {@code
|
||||||
* if this {@code TreeTypeAdapter} has a {@link #serializer}; otherwise returns
|
* TreeTypeAdapter} has a {@link #serializer}; otherwise returns the delegate.
|
||||||
* the delegate.
|
|
||||||
*/
|
*/
|
||||||
@Override public TypeAdapter<T> getSerializationDelegate() {
|
@Override
|
||||||
|
public TypeAdapter<T> getSerializationDelegate() {
|
||||||
return serializer != null ? this : delegate();
|
return serializer != null ? this : delegate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns a new factory that will match each type against {@code exactType}. */
|
||||||
* Returns a new factory that will match each type against {@code exactType}.
|
|
||||||
*/
|
|
||||||
public static TypeAdapterFactory newFactory(TypeToken<?> exactType, Object typeAdapter) {
|
public static TypeAdapterFactory newFactory(TypeToken<?> exactType, Object typeAdapter) {
|
||||||
return new SingleTypeFactory(typeAdapter, exactType, false, null);
|
return new SingleTypeFactory(typeAdapter, exactType, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns a new factory that will match each type and its raw type against {@code exactType}. */
|
||||||
* Returns a new factory that will match each type and its raw type against
|
|
||||||
* {@code exactType}.
|
|
||||||
*/
|
|
||||||
public static TypeAdapterFactory newFactoryWithMatchRawType(
|
public static TypeAdapterFactory newFactoryWithMatchRawType(
|
||||||
TypeToken<?> exactType, Object typeAdapter) {
|
TypeToken<?> exactType, Object typeAdapter) {
|
||||||
// only bother matching raw types if exact type is a raw type
|
// only bother matching raw types if exact type is a raw type
|
||||||
|
@ -132,8 +141,8 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new factory that will match each type's raw type for assignability
|
* Returns a new factory that will match each type's raw type for assignability to {@code
|
||||||
* to {@code hierarchyType}.
|
* hierarchyType}.
|
||||||
*/
|
*/
|
||||||
public static TypeAdapterFactory newTypeHierarchyFactory(
|
public static TypeAdapterFactory newTypeHierarchyFactory(
|
||||||
Class<?> hierarchyType, Object typeAdapter) {
|
Class<?> hierarchyType, Object typeAdapter) {
|
||||||
|
@ -147,14 +156,11 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
private final JsonSerializer<?> serializer;
|
private final JsonSerializer<?> serializer;
|
||||||
private final JsonDeserializer<?> deserializer;
|
private final JsonDeserializer<?> deserializer;
|
||||||
|
|
||||||
SingleTypeFactory(Object typeAdapter, TypeToken<?> exactType, boolean matchRawType,
|
SingleTypeFactory(
|
||||||
Class<?> hierarchyType) {
|
Object typeAdapter, TypeToken<?> exactType, boolean matchRawType, Class<?> hierarchyType) {
|
||||||
serializer = typeAdapter instanceof JsonSerializer
|
serializer = typeAdapter instanceof JsonSerializer ? (JsonSerializer<?>) typeAdapter : null;
|
||||||
? (JsonSerializer<?>) typeAdapter
|
deserializer =
|
||||||
: null;
|
typeAdapter instanceof JsonDeserializer ? (JsonDeserializer<?>) typeAdapter : null;
|
||||||
deserializer = typeAdapter instanceof JsonDeserializer
|
|
||||||
? (JsonDeserializer<?>) typeAdapter
|
|
||||||
: null;
|
|
||||||
$Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
|
$Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
|
||||||
this.exactType = exactType;
|
this.exactType = exactType;
|
||||||
this.matchRawType = matchRawType;
|
this.matchRawType = matchRawType;
|
||||||
|
@ -164,23 +170,29 @@ public final class TreeTypeAdapter<T> extends SerializationDelegatingTypeAdapter
|
||||||
@SuppressWarnings("unchecked") // guarded by typeToken.equals() call
|
@SuppressWarnings("unchecked") // guarded by typeToken.equals() call
|
||||||
@Override
|
@Override
|
||||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
boolean matches = exactType != null
|
boolean matches =
|
||||||
? exactType.equals(type) || (matchRawType && exactType.getType() == type.getRawType())
|
exactType != null
|
||||||
: hierarchyType.isAssignableFrom(type.getRawType());
|
? exactType.equals(type) || (matchRawType && exactType.getType() == type.getRawType())
|
||||||
|
: hierarchyType.isAssignableFrom(type.getRawType());
|
||||||
return matches
|
return matches
|
||||||
? new TreeTypeAdapter<>((JsonSerializer<T>) serializer,
|
? new TreeTypeAdapter<>(
|
||||||
(JsonDeserializer<T>) deserializer, gson, type, this)
|
(JsonSerializer<T>) serializer, (JsonDeserializer<T>) deserializer, gson, type, this)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class GsonContextImpl implements JsonSerializationContext, JsonDeserializationContext {
|
private final class GsonContextImpl
|
||||||
@Override public JsonElement serialize(Object src) {
|
implements JsonSerializationContext, JsonDeserializationContext {
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Object src) {
|
||||||
return gson.toJsonTree(src);
|
return gson.toJsonTree(src);
|
||||||
}
|
}
|
||||||
@Override public JsonElement serialize(Object src, Type typeOfSrc) {
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Object src, Type typeOfSrc) {
|
||||||
return gson.toJsonTree(src, typeOfSrc);
|
return gson.toJsonTree(src, typeOfSrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
|
@SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
|
||||||
public <R> R deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
|
public <R> R deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
|
||||||
|
|
|
@ -1,101 +1,103 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2011 Google Inc.
|
* Copyright (C) 2011 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.gson.internal.bind;
|
package com.google.gson.internal.bind;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.lang.reflect.TypeVariable;
|
import java.lang.reflect.TypeVariable;
|
||||||
|
|
||||||
final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
|
final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
|
||||||
private final Gson context;
|
private final Gson context;
|
||||||
private final TypeAdapter<T> delegate;
|
private final TypeAdapter<T> delegate;
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
|
||||||
TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
|
TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T read(JsonReader in) throws IOException {
|
public T read(JsonReader in) throws IOException {
|
||||||
return delegate.read(in);
|
return delegate.read(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, T value) throws IOException {
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
// Order of preference for choosing type adapters
|
// Order of preference for choosing type adapters
|
||||||
// First preference: a type adapter registered for the runtime type
|
// First preference: a type adapter registered for the runtime type
|
||||||
// Second preference: a type adapter registered for the declared type
|
// Second preference: a type adapter registered for the declared type
|
||||||
// Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
|
// Third preference: reflective type adapter for the runtime type
|
||||||
// Fourth preference: reflective type adapter for the declared type
|
// (if it is a subclass of the declared type)
|
||||||
|
// Fourth preference: reflective type adapter for the declared type
|
||||||
TypeAdapter<T> chosen = delegate;
|
|
||||||
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
|
TypeAdapter<T> chosen = delegate;
|
||||||
if (runtimeType != type) {
|
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
|
||||||
@SuppressWarnings("unchecked")
|
if (runtimeType != type) {
|
||||||
TypeAdapter<T> runtimeTypeAdapter = (TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
|
@SuppressWarnings("unchecked")
|
||||||
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any other
|
TypeAdapter<T> runtimeTypeAdapter =
|
||||||
// wrapping adapters, see https://github.com/google/gson/pull/1787#issuecomment-1222175189
|
(TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
|
||||||
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
|
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any
|
||||||
// The user registered a type adapter for the runtime type, so we will use that
|
// other wrapping adapters, see
|
||||||
chosen = runtimeTypeAdapter;
|
// https://github.com/google/gson/pull/1787#issuecomment-1222175189
|
||||||
} else if (!isReflective(delegate)) {
|
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
|
||||||
// The user registered a type adapter for Base class, so we prefer it over the
|
// The user registered a type adapter for the runtime type, so we will use that
|
||||||
// reflective type adapter for the runtime type
|
chosen = runtimeTypeAdapter;
|
||||||
chosen = delegate;
|
} else if (!isReflective(delegate)) {
|
||||||
} else {
|
// The user registered a type adapter for Base class, so we prefer it over the
|
||||||
// Use the type adapter for runtime type
|
// reflective type adapter for the runtime type
|
||||||
chosen = runtimeTypeAdapter;
|
chosen = delegate;
|
||||||
}
|
} else {
|
||||||
}
|
// Use the type adapter for runtime type
|
||||||
chosen.write(out, value);
|
chosen = runtimeTypeAdapter;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
chosen.write(out, value);
|
||||||
* Returns whether the type adapter uses reflection.
|
}
|
||||||
*
|
|
||||||
* @param typeAdapter the type adapter to check.
|
/**
|
||||||
*/
|
* Returns whether the type adapter uses reflection.
|
||||||
private static boolean isReflective(TypeAdapter<?> typeAdapter) {
|
*
|
||||||
// Run this in loop in case multiple delegating adapters are nested
|
* @param typeAdapter the type adapter to check.
|
||||||
while (typeAdapter instanceof SerializationDelegatingTypeAdapter) {
|
*/
|
||||||
TypeAdapter<?> delegate = ((SerializationDelegatingTypeAdapter<?>) typeAdapter).getSerializationDelegate();
|
private static boolean isReflective(TypeAdapter<?> typeAdapter) {
|
||||||
// Break if adapter does not delegate serialization
|
// Run this in loop in case multiple delegating adapters are nested
|
||||||
if (delegate == typeAdapter) {
|
while (typeAdapter instanceof SerializationDelegatingTypeAdapter) {
|
||||||
break;
|
TypeAdapter<?> delegate =
|
||||||
}
|
((SerializationDelegatingTypeAdapter<?>) typeAdapter).getSerializationDelegate();
|
||||||
typeAdapter = delegate;
|
// Break if adapter does not delegate serialization
|
||||||
}
|
if (delegate == typeAdapter) {
|
||||||
|
break;
|
||||||
return typeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter;
|
}
|
||||||
}
|
typeAdapter = delegate;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Finds a compatible runtime type if it is more specific
|
return typeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter;
|
||||||
*/
|
}
|
||||||
private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
|
|
||||||
if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) {
|
/** Finds a compatible runtime type if it is more specific */
|
||||||
type = value.getClass();
|
private static Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
|
||||||
}
|
if (value != null && (type instanceof Class<?> || type instanceof TypeVariable<?>)) {
|
||||||
return type;
|
type = value.getClass();
|
||||||
}
|
}
|
||||||
}
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,8 +15,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do NOT use any class in this package as they are meant for internal use in Gson.
|
* Do NOT use any class in this package as they are meant for internal use in Gson. These classes
|
||||||
* These classes will very likely change incompatibly in future versions. You have been warned.
|
* will very likely change incompatibly in future versions. You have been warned.
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh, Joel Leitch, Jesse Wilson
|
* @author Inderjeet Singh, Joel Leitch, Jesse Wilson
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.reflect.AccessibleObject;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
public class ReflectionHelper {
|
public class ReflectionHelper {
|
||||||
|
|
||||||
|
@ -31,7 +32,8 @@ public class ReflectionHelper {
|
||||||
static {
|
static {
|
||||||
RecordHelper instance;
|
RecordHelper instance;
|
||||||
try {
|
try {
|
||||||
// Try to construct the RecordSupportedHelper, if this fails, records are not supported on this JVM.
|
// Try to construct the RecordSupportedHelper, if this fails, records are not supported on
|
||||||
|
// this JVM.
|
||||||
instance = new RecordSupportedHelper();
|
instance = new RecordSupportedHelper();
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
instance = new RecordNotSupportedHelper();
|
instance = new RecordNotSupportedHelper();
|
||||||
|
@ -45,8 +47,10 @@ public class ReflectionHelper {
|
||||||
// Class was added in Java 9, therefore cannot use instanceof
|
// Class was added in Java 9, therefore cannot use instanceof
|
||||||
if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
|
if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
|
||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
String troubleshootingId = message != null && message.contains("to module com.google.gson")
|
String troubleshootingId =
|
||||||
? "reflection-inaccessible-to-module-gson" : "reflection-inaccessible";
|
message != null && message.contains("to module com.google.gson")
|
||||||
|
? "reflection-inaccessible-to-module-gson"
|
||||||
|
: "reflection-inaccessible";
|
||||||
return "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId);
|
return "\nSee " + TroubleshootingGuide.createUrl(troubleshootingId);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
|
@ -55,7 +59,8 @@ public class ReflectionHelper {
|
||||||
/**
|
/**
|
||||||
* Internal implementation of making an {@link AccessibleObject} accessible.
|
* Internal implementation of making an {@link AccessibleObject} accessible.
|
||||||
*
|
*
|
||||||
* @param object the object that {@link AccessibleObject#setAccessible(boolean)} should be called on.
|
* @param object the object that {@link AccessibleObject#setAccessible(boolean)} should be called
|
||||||
|
* on.
|
||||||
* @throws JsonIOException if making the object accessible fails
|
* @throws JsonIOException if making the object accessible fails
|
||||||
*/
|
*/
|
||||||
public static void makeAccessible(AccessibleObject object) throws JsonIOException {
|
public static void makeAccessible(AccessibleObject object) throws JsonIOException {
|
||||||
|
@ -63,22 +68,26 @@ public class ReflectionHelper {
|
||||||
object.setAccessible(true);
|
object.setAccessible(true);
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
String description = getAccessibleObjectDescription(object, false);
|
String description = getAccessibleObjectDescription(object, false);
|
||||||
throw new JsonIOException("Failed making " + description + " accessible; either increase its visibility"
|
throw new JsonIOException(
|
||||||
+ " or write a custom TypeAdapter for its declaring type." + getInaccessibleTroubleshootingSuffix(exception),
|
"Failed making "
|
||||||
|
+ description
|
||||||
|
+ " accessible; either increase its visibility"
|
||||||
|
+ " or write a custom TypeAdapter for its declaring type."
|
||||||
|
+ getInaccessibleTroubleshootingSuffix(exception),
|
||||||
exception);
|
exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a short string describing the {@link AccessibleObject} in a human-readable way.
|
* Returns a short string describing the {@link AccessibleObject} in a human-readable way. The
|
||||||
* The result is normally shorter than {@link AccessibleObject#toString()} because it omits
|
* result is normally shorter than {@link AccessibleObject#toString()} because it omits modifiers
|
||||||
* modifiers (e.g. {@code final}) and uses simple names for constructor and method parameter
|
* (e.g. {@code final}) and uses simple names for constructor and method parameter types.
|
||||||
* types.
|
|
||||||
*
|
*
|
||||||
* @param object object to describe
|
* @param object object to describe
|
||||||
* @param uppercaseFirstLetter whether the first letter of the description should be uppercased
|
* @param uppercaseFirstLetter whether the first letter of the description should be uppercased
|
||||||
*/
|
*/
|
||||||
public static String getAccessibleObjectDescription(AccessibleObject object, boolean uppercaseFirstLetter) {
|
public static String getAccessibleObjectDescription(
|
||||||
|
AccessibleObject object, boolean uppercaseFirstLetter) {
|
||||||
String description;
|
String description;
|
||||||
|
|
||||||
if (object instanceof Field) {
|
if (object instanceof Field) {
|
||||||
|
@ -103,17 +112,14 @@ public class ReflectionHelper {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Creates a string representation for a field, omitting modifiers and the field type. */
|
||||||
* Creates a string representation for a field, omitting modifiers and
|
|
||||||
* the field type.
|
|
||||||
*/
|
|
||||||
public static String fieldToString(Field field) {
|
public static String fieldToString(Field field) {
|
||||||
return field.getDeclaringClass().getName() + "#" + field.getName();
|
return field.getDeclaringClass().getName() + "#" + field.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a string representation for a constructor.
|
* Creates a string representation for a constructor. E.g.: {@code java.lang.String(char[], int,
|
||||||
* E.g.: {@code java.lang.String(char[], int, int)}
|
* int)}
|
||||||
*/
|
*/
|
||||||
public static String constructorToString(Constructor<?> constructor) {
|
public static String constructorToString(Constructor<?> constructor) {
|
||||||
StringBuilder stringBuilder = new StringBuilder(constructor.getDeclaringClass().getName());
|
StringBuilder stringBuilder = new StringBuilder(constructor.getDeclaringClass().getName());
|
||||||
|
@ -122,13 +128,15 @@ public class ReflectionHelper {
|
||||||
return stringBuilder.toString();
|
return stringBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Ideally parameter type would be java.lang.reflect.Executable, but that was added in Java 8
|
// Ideally parameter type would be java.lang.reflect.Executable, but that was added in Java 8
|
||||||
private static void appendExecutableParameters(AccessibleObject executable, StringBuilder stringBuilder) {
|
private static void appendExecutableParameters(
|
||||||
|
AccessibleObject executable, StringBuilder stringBuilder) {
|
||||||
stringBuilder.append('(');
|
stringBuilder.append('(');
|
||||||
|
|
||||||
Class<?>[] parameters = (executable instanceof Method)
|
Class<?>[] parameters =
|
||||||
? ((Method) executable).getParameterTypes()
|
(executable instanceof Method)
|
||||||
: ((Constructor<?>) executable).getParameterTypes();
|
? ((Method) executable).getParameterTypes()
|
||||||
|
: ((Constructor<?>) executable).getParameterTypes();
|
||||||
for (int i = 0; i < parameters.length; i++) {
|
for (int i = 0; i < parameters.length; i++) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
stringBuilder.append(", ");
|
stringBuilder.append(", ");
|
||||||
|
@ -139,23 +147,33 @@ public class ReflectionHelper {
|
||||||
stringBuilder.append(')');
|
stringBuilder.append(')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isStatic(Class<?> clazz) {
|
||||||
|
return Modifier.isStatic(clazz.getModifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the class is anonymous or a non-static local class. */
|
||||||
|
public static boolean isAnonymousOrNonStaticLocal(Class<?> clazz) {
|
||||||
|
return !isStatic(clazz) && (clazz.isAnonymousClass() || clazz.isLocalClass());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries making the constructor accessible, returning an exception message
|
* Tries making the constructor accessible, returning an exception message if this fails.
|
||||||
* if this fails.
|
|
||||||
*
|
*
|
||||||
* @param constructor constructor to make accessible
|
* @param constructor constructor to make accessible
|
||||||
* @return exception message; {@code null} if successful, non-{@code null} if
|
* @return exception message; {@code null} if successful, non-{@code null} if unsuccessful
|
||||||
* unsuccessful
|
|
||||||
*/
|
*/
|
||||||
public static String tryMakeAccessible(Constructor<?> constructor) {
|
public static String tryMakeAccessible(Constructor<?> constructor) {
|
||||||
try {
|
try {
|
||||||
constructor.setAccessible(true);
|
constructor.setAccessible(true);
|
||||||
return null;
|
return null;
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
return "Failed making constructor '" + constructorToString(constructor) + "' accessible;"
|
return "Failed making constructor '"
|
||||||
+ " either increase its visibility or write a custom InstanceCreator or TypeAdapter for"
|
+ constructorToString(constructor)
|
||||||
|
+ "' accessible; either increase its visibility or write a custom InstanceCreator or"
|
||||||
|
+ " TypeAdapter for its declaring type: "
|
||||||
// Include the message since it might contain more detailed information
|
// Include the message since it might contain more detailed information
|
||||||
+ " its declaring type: " + exception.getMessage() + getInaccessibleTroubleshootingSuffix(exception);
|
+ exception.getMessage()
|
||||||
|
+ getInaccessibleTroubleshootingSuffix(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,26 +197,28 @@ public class ReflectionHelper {
|
||||||
|
|
||||||
public static RuntimeException createExceptionForUnexpectedIllegalAccess(
|
public static RuntimeException createExceptionForUnexpectedIllegalAccess(
|
||||||
IllegalAccessException exception) {
|
IllegalAccessException exception) {
|
||||||
throw new RuntimeException("Unexpected IllegalAccessException occurred (Gson " + GsonBuildConfig.VERSION + ")."
|
throw new RuntimeException(
|
||||||
+ " Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If you are not using"
|
"Unexpected IllegalAccessException occurred (Gson "
|
||||||
+ " ReflectionAccessFilter, report this to the Gson maintainers.",
|
+ GsonBuildConfig.VERSION
|
||||||
|
+ "). Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If"
|
||||||
|
+ " you are not using ReflectionAccessFilter, report this to the Gson maintainers.",
|
||||||
exception);
|
exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static RuntimeException createExceptionForRecordReflectionException(
|
private static RuntimeException createExceptionForRecordReflectionException(
|
||||||
ReflectiveOperationException exception) {
|
ReflectiveOperationException exception) {
|
||||||
throw new RuntimeException("Unexpected ReflectiveOperationException occurred"
|
throw new RuntimeException(
|
||||||
+ " (Gson " + GsonBuildConfig.VERSION + ")."
|
"Unexpected ReflectiveOperationException occurred"
|
||||||
|
+ " (Gson "
|
||||||
|
+ GsonBuildConfig.VERSION
|
||||||
|
+ ")."
|
||||||
+ " To support Java records, reflection is utilized to read out information"
|
+ " To support Java records, reflection is utilized to read out information"
|
||||||
+ " about records. All these invocations happens after it is established"
|
+ " about records. All these invocations happens after it is established"
|
||||||
+ " that records exist in the JVM. This exception is unexpected behavior.",
|
+ " that records exist in the JVM. This exception is unexpected behavior.",
|
||||||
exception);
|
exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Internal abstraction over reflection when Records are supported. */
|
||||||
* Internal abstraction over reflection when Records are supported.
|
|
||||||
*/
|
|
||||||
private abstract static class RecordHelper {
|
private abstract static class RecordHelper {
|
||||||
abstract boolean isRecord(Class<?> clazz);
|
abstract boolean isRecord(Class<?> clazz);
|
||||||
|
|
||||||
|
@ -254,8 +274,8 @@ public class ReflectionHelper {
|
||||||
for (int i = 0; i < recordComponents.length; i++) {
|
for (int i = 0; i < recordComponents.length; i++) {
|
||||||
recordComponentTypes[i] = (Class<?>) getType.invoke(recordComponents[i]);
|
recordComponentTypes[i] = (Class<?>) getType.invoke(recordComponents[i]);
|
||||||
}
|
}
|
||||||
// Uses getDeclaredConstructor because implicit constructor has same visibility as record and might
|
// Uses getDeclaredConstructor because implicit constructor has same visibility as record
|
||||||
// therefore not be public
|
// and might therefore not be public
|
||||||
return raw.getDeclaredConstructor(recordComponentTypes);
|
return raw.getDeclaredConstructor(recordComponentTypes);
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw createExceptionForRecordReflectionException(e);
|
throw createExceptionForRecordReflectionException(e);
|
||||||
|
@ -265,8 +285,9 @@ public class ReflectionHelper {
|
||||||
@Override
|
@Override
|
||||||
public Method getAccessor(Class<?> raw, Field field) {
|
public Method getAccessor(Class<?> raw, Field field) {
|
||||||
try {
|
try {
|
||||||
// Records consists of record components, each with a unique name, a corresponding field and accessor method
|
// Records consists of record components, each with a unique name, a corresponding field and
|
||||||
// with the same name. Ref.: https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10.3
|
// accessor method with the same name. Ref.:
|
||||||
|
// https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10.3
|
||||||
return raw.getMethod(field.getName());
|
return raw.getMethod(field.getName());
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw createExceptionForRecordReflectionException(e);
|
throw createExceptionForRecordReflectionException(e);
|
||||||
|
@ -274,9 +295,7 @@ public class ReflectionHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Instance used when records are not supported */
|
||||||
* Instance used when records are not supported
|
|
||||||
*/
|
|
||||||
private static class RecordNotSupportedHelper extends RecordHelper {
|
private static class RecordNotSupportedHelper extends RecordHelper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -287,19 +306,19 @@ public class ReflectionHelper {
|
||||||
@Override
|
@Override
|
||||||
String[] getRecordComponentNames(Class<?> clazz) {
|
String[] getRecordComponentNames(Class<?> clazz) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Records are not supported on this JVM, this method should not be called");
|
"Records are not supported on this JVM, this method should not be called");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
<T> Constructor<T> getCanonicalRecordConstructor(Class<T> raw) {
|
<T> Constructor<T> getCanonicalRecordConstructor(Class<T> raw) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Records are not supported on this JVM, this method should not be called");
|
"Records are not supported on this JVM, this method should not be called");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Method getAccessor(Class<?> raw, Field field) {
|
public Method getAccessor(Class<?> raw, Field field) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Records are not supported on this JVM, this method should not be called");
|
"Records are not supported on this JVM, this method should not be called");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,27 +29,29 @@ import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter for java.sql.Date. Although this class appears stateless, it is not.
|
* Adapter for java.sql.Date. Although this class appears stateless, it is not. DateFormat captures
|
||||||
* DateFormat captures its time zone and locale when it is created, which gives
|
* its time zone and locale when it is created, which gives this class state. DateFormat isn't
|
||||||
* this class state. DateFormat isn't thread safe either, so this class has
|
* thread safe either, so this class has to synchronize its read and write methods.
|
||||||
* to synchronize its read and write methods.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("JavaUtilDate")
|
@SuppressWarnings("JavaUtilDate")
|
||||||
final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
|
final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
|
||||||
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
static final TypeAdapterFactory FACTORY =
|
||||||
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
new TypeAdapterFactory() {
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
||||||
return typeToken.getRawType() == java.sql.Date.class
|
@Override
|
||||||
? (TypeAdapter<T>) new SqlDateTypeAdapter() : null;
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
}
|
return typeToken.getRawType() == java.sql.Date.class
|
||||||
};
|
? (TypeAdapter<T>) new SqlDateTypeAdapter()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final DateFormat format = new SimpleDateFormat("MMM d, yyyy");
|
private final DateFormat format = new SimpleDateFormat("MMM d, yyyy");
|
||||||
|
|
||||||
private SqlDateTypeAdapter() {
|
private SqlDateTypeAdapter() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public java.sql.Date read(JsonReader in) throws IOException {
|
public java.sql.Date read(JsonReader in) throws IOException {
|
||||||
|
@ -58,14 +60,17 @@ final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String s = in.nextString();
|
String s = in.nextString();
|
||||||
try {
|
synchronized (this) {
|
||||||
Date utilDate;
|
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
|
||||||
synchronized (this) {
|
try {
|
||||||
utilDate = format.parse(s);
|
Date utilDate = format.parse(s);
|
||||||
|
return new java.sql.Date(utilDate.getTime());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new JsonSyntaxException(
|
||||||
|
"Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e);
|
||||||
|
} finally {
|
||||||
|
format.setTimeZone(originalTimeZone); // Restore the original time zone after parsing
|
||||||
}
|
}
|
||||||
return new java.sql.Date(utilDate.getTime());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,44 +30,53 @@ import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter for java.sql.Time. Although this class appears stateless, it is not.
|
* Adapter for java.sql.Time. Although this class appears stateless, it is not. DateFormat captures
|
||||||
* DateFormat captures its time zone and locale when it is created, which gives
|
* its time zone and locale when it is created, which gives this class state. DateFormat isn't
|
||||||
* this class state. DateFormat isn't thread safe either, so this class has
|
* thread safe either, so this class has to synchronize its read and write methods.
|
||||||
* to synchronize its read and write methods.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("JavaUtilDate")
|
@SuppressWarnings("JavaUtilDate")
|
||||||
final class SqlTimeTypeAdapter extends TypeAdapter<Time> {
|
final class SqlTimeTypeAdapter extends TypeAdapter<Time> {
|
||||||
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
static final TypeAdapterFactory FACTORY =
|
||||||
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
new TypeAdapterFactory() {
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
||||||
return typeToken.getRawType() == Time.class ? (TypeAdapter<T>) new SqlTimeTypeAdapter() : null;
|
@Override
|
||||||
}
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
};
|
return typeToken.getRawType() == Time.class
|
||||||
|
? (TypeAdapter<T>) new SqlTimeTypeAdapter()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final DateFormat format = new SimpleDateFormat("hh:mm:ss a");
|
private final DateFormat format = new SimpleDateFormat("hh:mm:ss a");
|
||||||
|
|
||||||
private SqlTimeTypeAdapter() {
|
private SqlTimeTypeAdapter() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Time read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public Time read(JsonReader in) throws IOException {
|
||||||
if (in.peek() == JsonToken.NULL) {
|
if (in.peek() == JsonToken.NULL) {
|
||||||
in.nextNull();
|
in.nextNull();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String s = in.nextString();
|
String s = in.nextString();
|
||||||
try {
|
synchronized (this) {
|
||||||
synchronized (this) {
|
TimeZone originalTimeZone = format.getTimeZone(); // Save the original time zone
|
||||||
|
try {
|
||||||
Date date = format.parse(s);
|
Date date = format.parse(s);
|
||||||
return new Time(date.getTime());
|
return new Time(date.getTime());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new JsonSyntaxException(
|
||||||
|
"Failed parsing '" + s + "' as SQL Time; at path " + in.getPreviousPath(), e);
|
||||||
|
} finally {
|
||||||
|
format.setTimeZone(originalTimeZone); // Restore the original time zone
|
||||||
}
|
}
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Time; at path " + in.getPreviousPath(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, Time value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, Time value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
out.nullValue();
|
out.nullValue();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -22,24 +22,25 @@ import com.google.gson.TypeAdapterFactory;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@SuppressWarnings("JavaUtilDate")
|
@SuppressWarnings("JavaUtilDate")
|
||||||
class SqlTimestampTypeAdapter extends TypeAdapter<Timestamp> {
|
class SqlTimestampTypeAdapter extends TypeAdapter<Timestamp> {
|
||||||
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
static final TypeAdapterFactory FACTORY =
|
||||||
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
new TypeAdapterFactory() {
|
||||||
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
|
||||||
if (typeToken.getRawType() == Timestamp.class) {
|
@Override
|
||||||
final TypeAdapter<Date> dateTypeAdapter = gson.getAdapter(Date.class);
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
|
||||||
return (TypeAdapter<T>) new SqlTimestampTypeAdapter(dateTypeAdapter);
|
if (typeToken.getRawType() == Timestamp.class) {
|
||||||
} else {
|
final TypeAdapter<Date> dateTypeAdapter = gson.getAdapter(Date.class);
|
||||||
return null;
|
return (TypeAdapter<T>) new SqlTimestampTypeAdapter(dateTypeAdapter);
|
||||||
}
|
} else {
|
||||||
}
|
return null;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final TypeAdapter<Date> dateTypeAdapter;
|
private final TypeAdapter<Date> dateTypeAdapter;
|
||||||
|
|
||||||
|
|
|
@ -16,29 +16,23 @@
|
||||||
|
|
||||||
package com.google.gson.internal.sql;
|
package com.google.gson.internal.sql;
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import com.google.gson.TypeAdapterFactory;
|
|
||||||
import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates access to {@code java.sql} types, to allow Gson to
|
* Encapsulates access to {@code java.sql} types, to allow Gson to work without the {@code java.sql}
|
||||||
* work without the {@code java.sql} module being present.
|
* module being present. No {@link ClassNotFoundException}s will be thrown in case the {@code
|
||||||
* No {@link ClassNotFoundException}s will be thrown in case
|
* java.sql} module is not present.
|
||||||
* the {@code java.sql} module is not present.
|
|
||||||
*
|
*
|
||||||
* <p>If {@link #SUPPORTS_SQL_TYPES} is {@code true}, all other
|
* <p>If {@link #SUPPORTS_SQL_TYPES} is {@code true}, all other constants of this class will be
|
||||||
* constants of this class will be non-{@code null}. However, if
|
* non-{@code null}. However, if it is {@code false} all other constants will be {@code null} and
|
||||||
* it is {@code false} all other constants will be {@code null} and
|
|
||||||
* there will be no support for {@code java.sql} types.
|
* there will be no support for {@code java.sql} types.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("JavaUtilDate")
|
@SuppressWarnings("JavaUtilDate")
|
||||||
public final class SqlTypesSupport {
|
public final class SqlTypesSupport {
|
||||||
/**
|
/** {@code true} if {@code java.sql} types are supported, {@code false} otherwise */
|
||||||
* {@code true} if {@code java.sql} types are supported,
|
|
||||||
* {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public static final boolean SUPPORTS_SQL_TYPES;
|
public static final boolean SUPPORTS_SQL_TYPES;
|
||||||
|
|
||||||
public static final DateType<? extends Date> DATE_DATE_TYPE;
|
public static final DateType<? extends Date> DATE_DATE_TYPE;
|
||||||
|
@ -59,16 +53,20 @@ public final class SqlTypesSupport {
|
||||||
SUPPORTS_SQL_TYPES = sqlTypesSupport;
|
SUPPORTS_SQL_TYPES = sqlTypesSupport;
|
||||||
|
|
||||||
if (SUPPORTS_SQL_TYPES) {
|
if (SUPPORTS_SQL_TYPES) {
|
||||||
DATE_DATE_TYPE = new DateType<java.sql.Date>(java.sql.Date.class) {
|
DATE_DATE_TYPE =
|
||||||
@Override protected java.sql.Date deserialize(Date date) {
|
new DateType<java.sql.Date>(java.sql.Date.class) {
|
||||||
return new java.sql.Date(date.getTime());
|
@Override
|
||||||
}
|
protected java.sql.Date deserialize(Date date) {
|
||||||
};
|
return new java.sql.Date(date.getTime());
|
||||||
TIMESTAMP_DATE_TYPE = new DateType<Timestamp>(Timestamp.class) {
|
}
|
||||||
@Override protected Timestamp deserialize(Date date) {
|
};
|
||||||
return new Timestamp(date.getTime());
|
TIMESTAMP_DATE_TYPE =
|
||||||
}
|
new DateType<Timestamp>(Timestamp.class) {
|
||||||
};
|
@Override
|
||||||
|
protected Timestamp deserialize(Date date) {
|
||||||
|
return new Timestamp(date.getTime());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
DATE_FACTORY = SqlDateTypeAdapter.FACTORY;
|
DATE_FACTORY = SqlDateTypeAdapter.FACTORY;
|
||||||
TIME_FACTORY = SqlTimeTypeAdapter.FACTORY;
|
TIME_FACTORY = SqlTimeTypeAdapter.FACTORY;
|
||||||
|
@ -83,6 +81,5 @@ public final class SqlTypesSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlTypesSupport() {
|
private SqlTypesSupport() {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@
|
||||||
* This package provides the {@link com.google.gson.Gson} class to convert Json to Java and
|
* This package provides the {@link com.google.gson.Gson} class to convert Json to Java and
|
||||||
* vice-versa.
|
* vice-versa.
|
||||||
*
|
*
|
||||||
* <p>The primary class to use is {@link com.google.gson.Gson} which can be constructed with
|
* <p>The primary class to use is {@link com.google.gson.Gson} which can be constructed with {@code
|
||||||
* {@code new Gson()} (using default settings) or by using {@link com.google.gson.GsonBuilder}
|
* new Gson()} (using default settings) or by using {@link com.google.gson.GsonBuilder} (to
|
||||||
* (to configure various options such as using versioning and so on).</p>
|
* configure various options such as using versioning and so on).
|
||||||
*
|
*
|
||||||
* @author Inderjeet Singh, Joel Leitch
|
* @author Inderjeet Singh, Joel Leitch
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,28 +28,24 @@ import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a generic type {@code T}. Java doesn't yet provide a way to
|
* Represents a generic type {@code T}. Java doesn't yet provide a way to represent generic types,
|
||||||
* represent generic types, so this class does. Forces clients to create a
|
* so this class does. Forces clients to create a subclass of this class which enables retrieval the
|
||||||
* subclass of this class which enables retrieval the type information even at
|
* type information even at runtime.
|
||||||
* runtime.
|
|
||||||
*
|
*
|
||||||
* <p>For example, to create a type literal for {@code List<String>}, you can
|
* <p>For example, to create a type literal for {@code List<String>}, you can create an empty
|
||||||
* create an empty anonymous class:
|
* anonymous class:
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>{@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
|
||||||
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
|
|
||||||
*
|
*
|
||||||
* <p>Capturing a type variable as type argument of an anonymous {@code TypeToken}
|
* <p>Capturing a type variable as type argument of an anonymous {@code TypeToken} subclass is not
|
||||||
* subclass is not allowed, for example {@code TypeToken<List<T>>}.
|
* allowed, for example {@code TypeToken<List<T>>}. Due to type erasure the runtime type of a type
|
||||||
* Due to type erasure the runtime type of a type variable is not available
|
* variable is not available to Gson and therefore it cannot provide the functionality one might
|
||||||
* to Gson and therefore it cannot provide the functionality one might expect.
|
* expect. This would give a false sense of type-safety at compile time and could lead to an
|
||||||
* This would give a false sense of type-safety at compile time and could
|
* unexpected {@code ClassCastException} at runtime.
|
||||||
* lead to an unexpected {@code ClassCastException} at runtime.
|
|
||||||
*
|
*
|
||||||
* <p>If the type arguments of the parameterized type are only available at
|
* <p>If the type arguments of the parameterized type are only available at runtime, for example
|
||||||
* runtime, for example when you want to create a {@code List<E>} based on
|
* when you want to create a {@code List<E>} based on a {@code Class<E>} representing the element
|
||||||
* a {@code Class<E>} representing the element type, the method
|
* type, the method {@link #getParameterized(Type, Type...)} can be used.
|
||||||
* {@link #getParameterized(Type, Type...)} can be used.
|
|
||||||
*
|
*
|
||||||
* @author Bob Lee
|
* @author Bob Lee
|
||||||
* @author Sven Mawson
|
* @author Sven Mawson
|
||||||
|
@ -61,19 +57,17 @@ public class TypeToken<T> {
|
||||||
private final int hashCode;
|
private final int hashCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new type literal. Derives represented class from type
|
* Constructs a new type literal. Derives represented class from type parameter.
|
||||||
* parameter.
|
|
||||||
*
|
*
|
||||||
* <p>Clients create an empty anonymous subclass. Doing so embeds the type
|
* <p>Clients create an empty anonymous subclass. Doing so embeds the type parameter in the
|
||||||
* parameter in the anonymous class's type hierarchy so we can reconstitute it
|
* anonymous class's type hierarchy so we can reconstitute it at runtime despite erasure, for
|
||||||
* at runtime despite erasure, for example:
|
* example:
|
||||||
* <p>
|
|
||||||
* {@code new TypeToken<List<String>>() {}}
|
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException
|
* <p>{@code new TypeToken<List<String>>() {}}
|
||||||
* If the anonymous {@code TypeToken} subclass captures a type variable,
|
*
|
||||||
* for example {@code TypeToken<List<T>>}. See the {@code TypeToken}
|
* @throws IllegalArgumentException If the anonymous {@code TypeToken} subclass captures a type
|
||||||
* class documentation for more details.
|
* variable, for example {@code TypeToken<List<T>>}. See the {@code TypeToken} class
|
||||||
|
* documentation for more details.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected TypeToken() {
|
protected TypeToken() {
|
||||||
|
@ -82,9 +76,7 @@ public class TypeToken<T> {
|
||||||
this.hashCode = type.hashCode();
|
this.hashCode = type.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Unsafe. Constructs a type literal manually. */
|
||||||
* Unsafe. Constructs a type literal manually.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private TypeToken(Type type) {
|
private TypeToken(Type type) {
|
||||||
this.type = $Gson$Types.canonicalize(Objects.requireNonNull(type));
|
this.type = $Gson$Types.canonicalize(Objects.requireNonNull(type));
|
||||||
|
@ -97,9 +89,8 @@ public class TypeToken<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that {@code this} is an instance of a direct subclass of TypeToken and
|
* Verifies that {@code this} is an instance of a direct subclass of TypeToken and returns the
|
||||||
* returns the type argument for {@code T} in {@link $Gson$Types#canonicalize
|
* type argument for {@code T} in {@link $Gson$Types#canonicalize canonical form}.
|
||||||
* canonical form}.
|
|
||||||
*/
|
*/
|
||||||
private Type getTypeTokenTypeArgument() {
|
private Type getTypeTokenTypeArgument() {
|
||||||
Type superclass = getClass().getGenericSuperclass();
|
Type superclass = getClass().getGenericSuperclass();
|
||||||
|
@ -116,10 +107,11 @@ public class TypeToken<T> {
|
||||||
}
|
}
|
||||||
// Check for raw TypeToken as superclass
|
// Check for raw TypeToken as superclass
|
||||||
else if (superclass == TypeToken.class) {
|
else if (superclass == TypeToken.class) {
|
||||||
throw new IllegalStateException("TypeToken must be created with a type argument: new TypeToken<...>() {};"
|
throw new IllegalStateException(
|
||||||
+ " When using code shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved."
|
"TypeToken must be created with a type argument: new TypeToken<...>() {}; When using code"
|
||||||
+ "\nSee " + TroubleshootingGuide.createUrl("type-token-raw")
|
+ " shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved."
|
||||||
);
|
+ "\nSee "
|
||||||
|
+ TroubleshootingGuide.createUrl("type-token-raw"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// User created subclass of subclass of TypeToken
|
// User created subclass of subclass of TypeToken
|
||||||
|
@ -129,9 +121,13 @@ public class TypeToken<T> {
|
||||||
private static void verifyNoTypeVariable(Type type) {
|
private static void verifyNoTypeVariable(Type type) {
|
||||||
if (type instanceof TypeVariable) {
|
if (type instanceof TypeVariable) {
|
||||||
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
|
TypeVariable<?> typeVariable = (TypeVariable<?>) type;
|
||||||
throw new IllegalArgumentException("TypeToken type argument must not contain a type variable; captured type variable "
|
throw new IllegalArgumentException(
|
||||||
+ typeVariable.getName() + " declared by " + typeVariable.getGenericDeclaration()
|
"TypeToken type argument must not contain a type variable; captured type variable "
|
||||||
+ "\nSee " + TroubleshootingGuide.createUrl("typetoken-type-variable"));
|
+ typeVariable.getName()
|
||||||
|
+ " declared by "
|
||||||
|
+ typeVariable.getGenericDeclaration()
|
||||||
|
+ "\nSee "
|
||||||
|
+ TroubleshootingGuide.createUrl("typetoken-type-variable"));
|
||||||
} else if (type instanceof GenericArrayType) {
|
} else if (type instanceof GenericArrayType) {
|
||||||
verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());
|
verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType());
|
||||||
} else if (type instanceof ParameterizedType) {
|
} else if (type instanceof ParameterizedType) {
|
||||||
|
@ -153,22 +149,20 @@ public class TypeToken<T> {
|
||||||
verifyNoTypeVariable(bound);
|
verifyNoTypeVariable(bound);
|
||||||
}
|
}
|
||||||
} else if (type == null) {
|
} else if (type == null) {
|
||||||
// Occurs in Eclipse IDE and certain Java versions (e.g. Java 11.0.18) when capturing type variable
|
// Occurs in Eclipse IDE and certain Java versions (e.g. Java 11.0.18) when capturing type
|
||||||
// declared by method of local class, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975
|
// variable declared by method of local class, see
|
||||||
throw new IllegalArgumentException("TypeToken captured `null` as type argument; probably a compiler / runtime bug");
|
// 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. */
|
||||||
* Returns the raw (non-generic) type for this type.
|
|
||||||
*/
|
|
||||||
public final Class<? super T> getRawType() {
|
public final Class<? super T> getRawType() {
|
||||||
return rawType;
|
return rawType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Gets underlying {@code Type} instance. */
|
||||||
* Gets underlying {@code Type} instance.
|
|
||||||
*/
|
|
||||||
public final Type getType() {
|
public final Type getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
@ -176,8 +170,7 @@ public class TypeToken<T> {
|
||||||
/**
|
/**
|
||||||
* Check if this type is assignable from the given class object.
|
* Check if this type is assignable from the given class object.
|
||||||
*
|
*
|
||||||
* @deprecated this implementation may be inconsistent with javac for types
|
* @deprecated this implementation may be inconsistent with javac for types with wildcards.
|
||||||
* with wildcards.
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public boolean isAssignableFrom(Class<?> cls) {
|
public boolean isAssignableFrom(Class<?> cls) {
|
||||||
|
@ -187,8 +180,7 @@ public class TypeToken<T> {
|
||||||
/**
|
/**
|
||||||
* Check if this type is assignable from the given Type.
|
* Check if this type is assignable from the given Type.
|
||||||
*
|
*
|
||||||
* @deprecated this implementation may be inconsistent with javac for types
|
* @deprecated this implementation may be inconsistent with javac for types with wildcards.
|
||||||
* with wildcards.
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public boolean isAssignableFrom(Type from) {
|
public boolean isAssignableFrom(Type from) {
|
||||||
|
@ -203,13 +195,12 @@ public class TypeToken<T> {
|
||||||
if (type instanceof Class<?>) {
|
if (type instanceof Class<?>) {
|
||||||
return rawType.isAssignableFrom($Gson$Types.getRawType(from));
|
return rawType.isAssignableFrom($Gson$Types.getRawType(from));
|
||||||
} else if (type instanceof ParameterizedType) {
|
} else if (type instanceof ParameterizedType) {
|
||||||
return isAssignableFrom(from, (ParameterizedType) type,
|
return isAssignableFrom(from, (ParameterizedType) type, new HashMap<String, Type>());
|
||||||
new HashMap<String, Type>());
|
|
||||||
} else if (type instanceof GenericArrayType) {
|
} else if (type instanceof GenericArrayType) {
|
||||||
return rawType.isAssignableFrom($Gson$Types.getRawType(from))
|
return rawType.isAssignableFrom($Gson$Types.getRawType(from))
|
||||||
&& isAssignableFrom(from, (GenericArrayType) type);
|
&& isAssignableFrom(from, (GenericArrayType) type);
|
||||||
} else {
|
} else {
|
||||||
throw buildUnexpectedTypeError(
|
throw buildUnsupportedTypeException(
|
||||||
type, Class.class, ParameterizedType.class, GenericArrayType.class);
|
type, Class.class, ParameterizedType.class, GenericArrayType.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,8 +208,7 @@ public class TypeToken<T> {
|
||||||
/**
|
/**
|
||||||
* Check if this type is assignable from the given type token.
|
* Check if this type is assignable from the given type token.
|
||||||
*
|
*
|
||||||
* @deprecated this implementation may be inconsistent with javac for types
|
* @deprecated this implementation may be inconsistent with javac for types with wildcards.
|
||||||
* with wildcards.
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public boolean isAssignableFrom(TypeToken<?> token) {
|
public boolean isAssignableFrom(TypeToken<?> token) {
|
||||||
|
@ -226,8 +216,8 @@ public class TypeToken<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private helper function that performs some assignability checks for
|
* Private helper function that performs some assignability checks for the provided
|
||||||
* the provided GenericArrayType.
|
* GenericArrayType.
|
||||||
*/
|
*/
|
||||||
private static boolean isAssignableFrom(Type from, GenericArrayType to) {
|
private static boolean isAssignableFrom(Type from, GenericArrayType to) {
|
||||||
Type toGenericComponentType = to.getGenericComponentType();
|
Type toGenericComponentType = to.getGenericComponentType();
|
||||||
|
@ -242,20 +232,17 @@ public class TypeToken<T> {
|
||||||
}
|
}
|
||||||
t = classType;
|
t = classType;
|
||||||
}
|
}
|
||||||
return isAssignableFrom(t, (ParameterizedType) toGenericComponentType,
|
return isAssignableFrom(
|
||||||
new HashMap<String, Type>());
|
t, (ParameterizedType) toGenericComponentType, new HashMap<String, Type>());
|
||||||
}
|
}
|
||||||
// No generic defined on "to"; therefore, return true and let other
|
// No generic defined on "to"; therefore, return true and let other
|
||||||
// checks determine assignability
|
// checks determine assignability
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Private recursive helper function to actually do the type-safe checking of assignability. */
|
||||||
* Private recursive helper function to actually do the type-safe checking
|
private static boolean isAssignableFrom(
|
||||||
* of assignability.
|
Type from, ParameterizedType to, Map<String, Type> typeVarMap) {
|
||||||
*/
|
|
||||||
private static boolean isAssignableFrom(Type from, ParameterizedType to,
|
|
||||||
Map<String, Type> typeVarMap) {
|
|
||||||
|
|
||||||
if (from == null) {
|
if (from == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -304,11 +291,11 @@ public class TypeToken<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if two parameterized types are exactly equal, under the variable
|
* Checks if two parameterized types are exactly equal, under the variable replacement described
|
||||||
* replacement described in the typeVarMap.
|
* in the typeVarMap.
|
||||||
*/
|
*/
|
||||||
private static boolean typeEquals(ParameterizedType from,
|
private static boolean typeEquals(
|
||||||
ParameterizedType to, Map<String, Type> typeVarMap) {
|
ParameterizedType from, ParameterizedType to, Map<String, Type> typeVarMap) {
|
||||||
if (from.getRawType().equals(to.getRawType())) {
|
if (from.getRawType().equals(to.getRawType())) {
|
||||||
Type[] fromArgs = from.getActualTypeArguments();
|
Type[] fromArgs = from.getActualTypeArguments();
|
||||||
Type[] toArgs = to.getActualTypeArguments();
|
Type[] toArgs = to.getActualTypeArguments();
|
||||||
|
@ -322,55 +309,54 @@ public class TypeToken<T> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AssertionError buildUnexpectedTypeError(
|
private static IllegalArgumentException buildUnsupportedTypeException(
|
||||||
Type token, Class<?>... expected) {
|
Type token, Class<?>... expected) {
|
||||||
|
|
||||||
// Build exception message
|
// Build exception message
|
||||||
StringBuilder exceptionMessage =
|
StringBuilder exceptionMessage = new StringBuilder("Unsupported type, expected one of: ");
|
||||||
new StringBuilder("Unexpected type. Expected one of: ");
|
|
||||||
for (Class<?> clazz : expected) {
|
for (Class<?> clazz : expected) {
|
||||||
exceptionMessage.append(clazz.getName()).append(", ");
|
exceptionMessage.append(clazz.getName()).append(", ");
|
||||||
}
|
}
|
||||||
exceptionMessage.append("but got: ").append(token.getClass().getName())
|
exceptionMessage
|
||||||
.append(", for type token: ").append(token.toString()).append('.');
|
.append("but got: ")
|
||||||
|
.append(token.getClass().getName())
|
||||||
|
.append(", for type token: ")
|
||||||
|
.append(token.toString());
|
||||||
|
|
||||||
return new AssertionError(exceptionMessage.toString());
|
return new IllegalArgumentException(exceptionMessage.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if two types are the same or are equivalent under a variable mapping
|
* Checks if two types are the same or are equivalent under a variable mapping given in the type
|
||||||
* given in the type map that was provided.
|
* map that was provided.
|
||||||
*/
|
*/
|
||||||
private static boolean matches(Type from, Type to, Map<String, Type> typeMap) {
|
private static boolean matches(Type from, Type to, Map<String, Type> typeMap) {
|
||||||
return to.equals(from)
|
return to.equals(from)
|
||||||
|| (from instanceof TypeVariable
|
|| (from instanceof TypeVariable
|
||||||
&& to.equals(typeMap.get(((TypeVariable<?>) from).getName())));
|
&& to.equals(typeMap.get(((TypeVariable<?>) from).getName())));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final int hashCode() {
|
@Override
|
||||||
|
public final int hashCode() {
|
||||||
return this.hashCode;
|
return this.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final boolean equals(Object o) {
|
@Override
|
||||||
return o instanceof TypeToken<?>
|
public final boolean equals(Object o) {
|
||||||
&& $Gson$Types.equals(type, ((TypeToken<?>) o).type);
|
return o instanceof TypeToken<?> && $Gson$Types.equals(type, ((TypeToken<?>) o).type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public final String toString() {
|
@Override
|
||||||
|
public final String toString() {
|
||||||
return $Gson$Types.typeToString(type);
|
return $Gson$Types.typeToString(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Gets type literal for the given {@code Type} instance. */
|
||||||
* Gets type literal for the given {@code Type} instance.
|
|
||||||
*/
|
|
||||||
public static TypeToken<?> get(Type type) {
|
public static TypeToken<?> get(Type type) {
|
||||||
return new TypeToken<>(type);
|
return new TypeToken<>(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Gets type literal for the given {@code Class} instance. */
|
||||||
* Gets type literal for the given {@code Class} instance.
|
|
||||||
*/
|
|
||||||
public static <T> TypeToken<T> get(Class<T> type) {
|
public static <T> TypeToken<T> get(Class<T> type) {
|
||||||
return new TypeToken<>(type);
|
return new TypeToken<>(type);
|
||||||
}
|
}
|
||||||
|
@ -380,20 +366,21 @@ public class TypeToken<T> {
|
||||||
* {@code rawType}. This is mainly intended for situations where the type arguments are not
|
* {@code rawType}. This is mainly intended for situations where the type arguments are not
|
||||||
* available at compile time. The following example shows how a type token for {@code Map<K, V>}
|
* available at compile time. The following example shows how a type token for {@code Map<K, V>}
|
||||||
* can be created:
|
* can be created:
|
||||||
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* Class<K> keyClass = ...;
|
* Class<K> keyClass = ...;
|
||||||
* Class<V> valueClass = ...;
|
* Class<V> valueClass = ...;
|
||||||
* TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
|
* TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
|
||||||
* }</pre>
|
* }</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.
|
* 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
|
* <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)}.
|
* simply delegates to {@link #get(Class)} and creates a {@code TypeToken(Class)}.
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException If {@code rawType} is not of type {@code Class}, or if the
|
||||||
* If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for
|
* type arguments are invalid for the raw type
|
||||||
* the raw type
|
|
||||||
*/
|
*/
|
||||||
public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) {
|
public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) {
|
||||||
Objects.requireNonNull(rawType);
|
Objects.requireNonNull(rawType);
|
||||||
|
@ -411,8 +398,12 @@ public class TypeToken<T> {
|
||||||
int expectedArgsCount = typeVariables.length;
|
int expectedArgsCount = typeVariables.length;
|
||||||
int actualArgsCount = typeArguments.length;
|
int actualArgsCount = typeArguments.length;
|
||||||
if (actualArgsCount != expectedArgsCount) {
|
if (actualArgsCount != expectedArgsCount) {
|
||||||
throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount +
|
throw new IllegalArgumentException(
|
||||||
" type arguments, but got " + actualArgsCount);
|
rawClass.getName()
|
||||||
|
+ " requires "
|
||||||
|
+ expectedArgsCount
|
||||||
|
+ " type arguments, but got "
|
||||||
|
+ actualArgsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For legacy reasons create a TypeToken(Class) if the type is not generic
|
// For legacy reasons create a TypeToken(Class) if the type is not generic
|
||||||
|
@ -422,12 +413,15 @@ public class TypeToken<T> {
|
||||||
|
|
||||||
// Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl
|
// Check for this here to avoid misleading exception thrown by ParameterizedTypeImpl
|
||||||
if ($Gson$Types.requiresOwnerType(rawType)) {
|
if ($Gson$Types.requiresOwnerType(rawType)) {
|
||||||
throw new IllegalArgumentException("Raw type " + rawClass.getName() + " is not supported because"
|
throw new IllegalArgumentException(
|
||||||
+ " it requires specifying an owner type");
|
"Raw type "
|
||||||
|
+ rawClass.getName()
|
||||||
|
+ " is not supported because it requires specifying an owner type");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < expectedArgsCount; i++) {
|
for (int i = 0; i < expectedArgsCount; i++) {
|
||||||
Type typeArgument = Objects.requireNonNull(typeArguments[i], "Type argument must not be null");
|
Type typeArgument =
|
||||||
|
Objects.requireNonNull(typeArguments[i], "Type argument must not be null");
|
||||||
Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
|
Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
|
||||||
TypeVariable<?> typeVariable = typeVariables[i];
|
TypeVariable<?> typeVariable = typeVariables[i];
|
||||||
|
|
||||||
|
@ -435,8 +429,13 @@ public class TypeToken<T> {
|
||||||
Class<?> rawBound = $Gson$Types.getRawType(bound);
|
Class<?> rawBound = $Gson$Types.getRawType(bound);
|
||||||
|
|
||||||
if (!rawBound.isAssignableFrom(rawTypeArgument)) {
|
if (!rawBound.isAssignableFrom(rawTypeArgument)) {
|
||||||
throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds"
|
throw new IllegalArgumentException(
|
||||||
+ " for type variable " + typeVariable + " declared by " + rawType);
|
"Type argument "
|
||||||
|
+ typeArgument
|
||||||
|
+ " does not satisfy bounds for type variable "
|
||||||
|
+ typeVariable
|
||||||
|
+ " declared by "
|
||||||
|
+ rawType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -23,49 +23,29 @@ package com.google.gson.stream;
|
||||||
* @since 1.6
|
* @since 1.6
|
||||||
*/
|
*/
|
||||||
final class JsonScope {
|
final class JsonScope {
|
||||||
|
private JsonScope() {}
|
||||||
|
|
||||||
/**
|
/** An array with no elements requires no separator before the next element. */
|
||||||
* An array with no elements requires no separators or newlines before
|
static final int EMPTY_ARRAY = 1;
|
||||||
* it is closed.
|
|
||||||
*/
|
|
||||||
static final int EMPTY_ARRAY = 1;
|
|
||||||
|
|
||||||
/**
|
/** An array with at least one value requires a separator before the next element. */
|
||||||
* An array with at least one value requires a comma and newline before
|
static final int NONEMPTY_ARRAY = 2;
|
||||||
* the next element.
|
|
||||||
*/
|
|
||||||
static final int NONEMPTY_ARRAY = 2;
|
|
||||||
|
|
||||||
/**
|
/** An object with no name/value pairs requires no separator before the next element. */
|
||||||
* An object with no name/value pairs requires no separators or newlines
|
static final int EMPTY_OBJECT = 3;
|
||||||
* before it is closed.
|
|
||||||
*/
|
|
||||||
static final int EMPTY_OBJECT = 3;
|
|
||||||
|
|
||||||
/**
|
/** An object whose most recent element is a key. The next element must be a value. */
|
||||||
* An object whose most recent element is a key. The next element must
|
static final int DANGLING_NAME = 4;
|
||||||
* be a value.
|
|
||||||
*/
|
|
||||||
static final int DANGLING_NAME = 4;
|
|
||||||
|
|
||||||
/**
|
/** An object with at least one name/value pair requires a separator before the next element. */
|
||||||
* An object with at least one name/value pair requires a comma and
|
static final int NONEMPTY_OBJECT = 5;
|
||||||
* newline before the next element.
|
|
||||||
*/
|
|
||||||
static final int NONEMPTY_OBJECT = 5;
|
|
||||||
|
|
||||||
/**
|
/** No top-level value has been started yet. */
|
||||||
* No object or array has been started.
|
static final int EMPTY_DOCUMENT = 6;
|
||||||
*/
|
|
||||||
static final int EMPTY_DOCUMENT = 6;
|
|
||||||
|
|
||||||
/**
|
/** A top-level value has already been started. */
|
||||||
* A document with at an array or object.
|
static final int NONEMPTY_DOCUMENT = 7;
|
||||||
*/
|
|
||||||
static final int NONEMPTY_DOCUMENT = 7;
|
|
||||||
|
|
||||||
/**
|
/** A document that's been closed and cannot be accessed. */
|
||||||
* A document that's been closed and cannot be accessed.
|
static final int CLOSED = 8;
|
||||||
*/
|
|
||||||
static final int CLOSED = 8;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,61 +25,52 @@ package com.google.gson.stream;
|
||||||
public enum JsonToken {
|
public enum JsonToken {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The opening of a JSON array. Written using {@link JsonWriter#beginArray}
|
* The opening of a JSON array. Written using {@link JsonWriter#beginArray} and read using {@link
|
||||||
* and read using {@link JsonReader#beginArray}.
|
* JsonReader#beginArray}.
|
||||||
*/
|
*/
|
||||||
BEGIN_ARRAY,
|
BEGIN_ARRAY,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The closing of a JSON array. Written using {@link JsonWriter#endArray}
|
* The closing of a JSON array. Written using {@link JsonWriter#endArray} and read using {@link
|
||||||
* and read using {@link JsonReader#endArray}.
|
* JsonReader#endArray}.
|
||||||
*/
|
*/
|
||||||
END_ARRAY,
|
END_ARRAY,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The opening of a JSON object. Written using {@link JsonWriter#beginObject}
|
* The opening of a JSON object. Written using {@link JsonWriter#beginObject} and read using
|
||||||
* and read using {@link JsonReader#beginObject}.
|
* {@link JsonReader#beginObject}.
|
||||||
*/
|
*/
|
||||||
BEGIN_OBJECT,
|
BEGIN_OBJECT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The closing of a JSON object. Written using {@link JsonWriter#endObject}
|
* The closing of a JSON object. Written using {@link JsonWriter#endObject} and read using {@link
|
||||||
* and read using {@link JsonReader#endObject}.
|
* JsonReader#endObject}.
|
||||||
*/
|
*/
|
||||||
END_OBJECT,
|
END_OBJECT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A JSON property name. Within objects, tokens alternate between names and
|
* A JSON property name. Within objects, tokens alternate between names and their values. Written
|
||||||
* their values. Written using {@link JsonWriter#name} and read using {@link
|
* using {@link JsonWriter#name} and read using {@link JsonReader#nextName}
|
||||||
* JsonReader#nextName}
|
|
||||||
*/
|
*/
|
||||||
NAME,
|
NAME,
|
||||||
|
|
||||||
/**
|
/** A JSON string. */
|
||||||
* A JSON string.
|
|
||||||
*/
|
|
||||||
STRING,
|
STRING,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A JSON number represented in this API by a Java {@code double}, {@code
|
* A JSON number represented in this API by a Java {@code double}, {@code long}, or {@code int}.
|
||||||
* long}, or {@code int}.
|
|
||||||
*/
|
*/
|
||||||
NUMBER,
|
NUMBER,
|
||||||
|
|
||||||
/**
|
/** A JSON {@code true} or {@code false}. */
|
||||||
* A JSON {@code true} or {@code false}.
|
|
||||||
*/
|
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
|
|
||||||
/**
|
/** A JSON {@code null}. */
|
||||||
* A JSON {@code null}.
|
|
||||||
*/
|
|
||||||
NULL,
|
NULL,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The end of the JSON stream. This sentinel value is returned by {@link
|
* The end of the JSON stream. This sentinel value is returned by {@link JsonReader#peek()} to
|
||||||
* JsonReader#peek()} to signal that the JSON-encoded value has no more
|
* signal that the JSON-encoded value has no more tokens.
|
||||||
* tokens.
|
|
||||||
*/
|
*/
|
||||||
END_DOCUMENT
|
END_DOCUMENT
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,20 +31,23 @@ import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This reader walks the elements of a JsonElement as if it was coming from a
|
* This reader walks the elements of a JsonElement as if it was coming from a character stream.
|
||||||
* character stream.
|
|
||||||
*
|
*
|
||||||
* @author Jesse Wilson
|
* @author Jesse Wilson
|
||||||
*/
|
*/
|
||||||
public final class JsonTreeReader extends JsonReader {
|
public final class JsonTreeReader extends JsonReader {
|
||||||
private static final Reader UNREADABLE_READER = new Reader() {
|
private static final Reader UNREADABLE_READER =
|
||||||
@Override public int read(char[] buffer, int offset, int count) {
|
new Reader() {
|
||||||
throw new AssertionError();
|
@Override
|
||||||
}
|
public int read(char[] buffer, int offset, int count) {
|
||||||
@Override public void close() {
|
throw new AssertionError();
|
||||||
throw new AssertionError();
|
}
|
||||||
}
|
|
||||||
};
|
@Override
|
||||||
|
public void close() {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
};
|
||||||
private static final Object SENTINEL_CLOSED = new Object();
|
private static final Object SENTINEL_CLOSED = new Object();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -69,14 +72,16 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
push(element);
|
push(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void beginArray() throws IOException {
|
@Override
|
||||||
|
public void beginArray() throws IOException {
|
||||||
expect(JsonToken.BEGIN_ARRAY);
|
expect(JsonToken.BEGIN_ARRAY);
|
||||||
JsonArray array = (JsonArray) peekStack();
|
JsonArray array = (JsonArray) peekStack();
|
||||||
push(array.iterator());
|
push(array.iterator());
|
||||||
pathIndices[stackSize - 1] = 0;
|
pathIndices[stackSize - 1] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void endArray() throws IOException {
|
@Override
|
||||||
|
public void endArray() throws IOException {
|
||||||
expect(JsonToken.END_ARRAY);
|
expect(JsonToken.END_ARRAY);
|
||||||
popStack(); // empty iterator
|
popStack(); // empty iterator
|
||||||
popStack(); // array
|
popStack(); // array
|
||||||
|
@ -85,13 +90,15 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void beginObject() throws IOException {
|
@Override
|
||||||
|
public void beginObject() throws IOException {
|
||||||
expect(JsonToken.BEGIN_OBJECT);
|
expect(JsonToken.BEGIN_OBJECT);
|
||||||
JsonObject object = (JsonObject) peekStack();
|
JsonObject object = (JsonObject) peekStack();
|
||||||
push(object.entrySet().iterator());
|
push(object.entrySet().iterator());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void endObject() throws IOException {
|
@Override
|
||||||
|
public void endObject() throws IOException {
|
||||||
expect(JsonToken.END_OBJECT);
|
expect(JsonToken.END_OBJECT);
|
||||||
pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected
|
pathNames[stackSize - 1] = null; // Free the last path name so that it can be garbage collected
|
||||||
popStack(); // empty iterator
|
popStack(); // empty iterator
|
||||||
|
@ -101,12 +108,16 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean hasNext() throws IOException {
|
@Override
|
||||||
|
public boolean hasNext() throws IOException {
|
||||||
JsonToken token = peek();
|
JsonToken token = peek();
|
||||||
return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY && token != JsonToken.END_DOCUMENT;
|
return token != JsonToken.END_OBJECT
|
||||||
|
&& token != JsonToken.END_ARRAY
|
||||||
|
&& token != JsonToken.END_DOCUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonToken peek() throws IOException {
|
@Override
|
||||||
|
public JsonToken peek() throws IOException {
|
||||||
if (stackSize == 0) {
|
if (stackSize == 0) {
|
||||||
return JsonToken.END_DOCUMENT;
|
return JsonToken.END_DOCUMENT;
|
||||||
}
|
}
|
||||||
|
@ -145,7 +156,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
} else if (o == SENTINEL_CLOSED) {
|
} else if (o == SENTINEL_CLOSED) {
|
||||||
throw new IllegalStateException("JsonReader is closed");
|
throw new IllegalStateException("JsonReader is closed");
|
||||||
} else {
|
} else {
|
||||||
throw new MalformedJsonException("Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
|
throw new MalformedJsonException(
|
||||||
|
"Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,11 +188,13 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String nextName() throws IOException {
|
@Override
|
||||||
|
public String nextName() throws IOException {
|
||||||
return nextName(false);
|
return nextName(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String nextString() throws IOException {
|
@Override
|
||||||
|
public String nextString() throws IOException {
|
||||||
JsonToken token = peek();
|
JsonToken token = peek();
|
||||||
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
|
if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -193,7 +207,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean nextBoolean() throws IOException {
|
@Override
|
||||||
|
public boolean nextBoolean() throws IOException {
|
||||||
expect(JsonToken.BOOLEAN);
|
expect(JsonToken.BOOLEAN);
|
||||||
boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
|
boolean result = ((JsonPrimitive) popStack()).getAsBoolean();
|
||||||
if (stackSize > 0) {
|
if (stackSize > 0) {
|
||||||
|
@ -202,7 +217,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void nextNull() throws IOException {
|
@Override
|
||||||
|
public void nextNull() throws IOException {
|
||||||
expect(JsonToken.NULL);
|
expect(JsonToken.NULL);
|
||||||
popStack();
|
popStack();
|
||||||
if (stackSize > 0) {
|
if (stackSize > 0) {
|
||||||
|
@ -210,7 +226,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public double nextDouble() throws IOException {
|
@Override
|
||||||
|
public double nextDouble() throws IOException {
|
||||||
JsonToken token = peek();
|
JsonToken token = peek();
|
||||||
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
|
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -227,7 +244,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public long nextLong() throws IOException {
|
@Override
|
||||||
|
public long nextLong() throws IOException {
|
||||||
JsonToken token = peek();
|
JsonToken token = peek();
|
||||||
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
|
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -241,7 +259,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int nextInt() throws IOException {
|
@Override
|
||||||
|
public int nextInt() throws IOException {
|
||||||
JsonToken token = peek();
|
JsonToken token = peek();
|
||||||
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
|
if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -268,12 +287,14 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void close() throws IOException {
|
@Override
|
||||||
stack = new Object[] { SENTINEL_CLOSED };
|
public void close() throws IOException {
|
||||||
|
stack = new Object[] {SENTINEL_CLOSED};
|
||||||
stackSize = 1;
|
stackSize = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void skipValue() throws IOException {
|
@Override
|
||||||
|
public void skipValue() throws IOException {
|
||||||
JsonToken peeked = peek();
|
JsonToken peeked = peek();
|
||||||
switch (peeked) {
|
switch (peeked) {
|
||||||
case NAME:
|
case NAME:
|
||||||
|
@ -297,7 +318,8 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String toString() {
|
@Override
|
||||||
|
public String toString() {
|
||||||
return getClass().getSimpleName() + locationString();
|
return getClass().getSimpleName() + locationString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,12 +367,14 @@ public final class JsonTreeReader extends JsonReader {
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String getPreviousPath() {
|
@Override
|
||||||
return getPath(true);
|
public String getPath() {
|
||||||
|
return getPath(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public String getPath() {
|
@Override
|
||||||
return getPath(false);
|
public String getPreviousPath() {
|
||||||
|
return getPath(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String locationString() {
|
private String locationString() {
|
||||||
|
|
|
@ -28,21 +28,26 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/** This writer creates a JsonElement. */
|
||||||
* This writer creates a JsonElement.
|
|
||||||
*/
|
|
||||||
public final class JsonTreeWriter extends JsonWriter {
|
public final class JsonTreeWriter extends JsonWriter {
|
||||||
private static final Writer UNWRITABLE_WRITER = new Writer() {
|
private static final Writer UNWRITABLE_WRITER =
|
||||||
@Override public void write(char[] buffer, int offset, int counter) {
|
new Writer() {
|
||||||
throw new AssertionError();
|
@Override
|
||||||
}
|
public void write(char[] buffer, int offset, int counter) {
|
||||||
@Override public void flush() {
|
throw new AssertionError();
|
||||||
throw new AssertionError();
|
}
|
||||||
}
|
|
||||||
@Override public void close() {
|
@Override
|
||||||
throw new AssertionError();
|
public void flush() {
|
||||||
}
|
throw new AssertionError();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** Added to the top of the stack when this writer is closed to cause following ops to fail. */
|
/** Added to the top of the stack when this writer is closed to cause following ops to fail. */
|
||||||
private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
|
private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
|
||||||
|
|
||||||
|
@ -59,9 +64,7 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
super(UNWRITABLE_WRITER);
|
super(UNWRITABLE_WRITER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the top level object produced by this writer. */
|
||||||
* Returns the top level object produced by this writer.
|
|
||||||
*/
|
|
||||||
public JsonElement get() {
|
public JsonElement get() {
|
||||||
if (!stack.isEmpty()) {
|
if (!stack.isEmpty()) {
|
||||||
throw new IllegalStateException("Expected one JSON element but was " + stack);
|
throw new IllegalStateException("Expected one JSON element but was " + stack);
|
||||||
|
@ -97,14 +100,16 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter beginArray() throws IOException {
|
@Override
|
||||||
|
public JsonWriter beginArray() throws IOException {
|
||||||
JsonArray array = new JsonArray();
|
JsonArray array = new JsonArray();
|
||||||
put(array);
|
put(array);
|
||||||
stack.add(array);
|
stack.add(array);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter endArray() throws IOException {
|
@Override
|
||||||
|
public JsonWriter endArray() throws IOException {
|
||||||
if (stack.isEmpty() || pendingName != null) {
|
if (stack.isEmpty() || pendingName != null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
@ -116,14 +121,16 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter beginObject() throws IOException {
|
@Override
|
||||||
|
public JsonWriter beginObject() throws IOException {
|
||||||
JsonObject object = new JsonObject();
|
JsonObject object = new JsonObject();
|
||||||
put(object);
|
put(object);
|
||||||
stack.add(object);
|
stack.add(object);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter endObject() throws IOException {
|
@Override
|
||||||
|
public JsonWriter endObject() throws IOException {
|
||||||
if (stack.isEmpty() || pendingName != null) {
|
if (stack.isEmpty() || pendingName != null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
@ -135,7 +142,8 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter name(String name) throws IOException {
|
@Override
|
||||||
|
public JsonWriter name(String name) throws IOException {
|
||||||
Objects.requireNonNull(name, "name == null");
|
Objects.requireNonNull(name, "name == null");
|
||||||
if (stack.isEmpty() || pendingName != null) {
|
if (stack.isEmpty() || pendingName != null) {
|
||||||
throw new IllegalStateException("Did not expect a name");
|
throw new IllegalStateException("Did not expect a name");
|
||||||
|
@ -148,7 +156,8 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
throw new IllegalStateException("Please begin an object before writing a name.");
|
throw new IllegalStateException("Please begin an object before writing a name.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter value(String value) throws IOException {
|
@Override
|
||||||
|
public JsonWriter value(String value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return nullValue();
|
return nullValue();
|
||||||
}
|
}
|
||||||
|
@ -156,21 +165,14 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter jsonValue(String value) throws IOException {
|
@Override
|
||||||
throw new UnsupportedOperationException();
|
public JsonWriter value(boolean value) throws IOException {
|
||||||
}
|
|
||||||
|
|
||||||
@Override public JsonWriter nullValue() throws IOException {
|
|
||||||
put(JsonNull.INSTANCE);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public JsonWriter value(boolean value) throws IOException {
|
|
||||||
put(new JsonPrimitive(value));
|
put(new JsonPrimitive(value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter value(Boolean value) throws IOException {
|
@Override
|
||||||
|
public JsonWriter value(Boolean value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return nullValue();
|
return nullValue();
|
||||||
}
|
}
|
||||||
|
@ -178,7 +180,8 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter value(float value) throws IOException {
|
@Override
|
||||||
|
public JsonWriter value(float value) throws IOException {
|
||||||
if (!isLenient() && (Float.isNaN(value) || Float.isInfinite(value))) {
|
if (!isLenient() && (Float.isNaN(value) || Float.isInfinite(value))) {
|
||||||
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
|
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
|
||||||
}
|
}
|
||||||
|
@ -186,7 +189,8 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter value(double value) throws IOException {
|
@Override
|
||||||
|
public JsonWriter value(double value) throws IOException {
|
||||||
if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
|
if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
|
||||||
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
|
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
|
||||||
}
|
}
|
||||||
|
@ -194,12 +198,14 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter value(long value) throws IOException {
|
@Override
|
||||||
|
public JsonWriter value(long value) throws IOException {
|
||||||
put(new JsonPrimitive(value));
|
put(new JsonPrimitive(value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public JsonWriter value(Number value) throws IOException {
|
@Override
|
||||||
|
public JsonWriter value(Number value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return nullValue();
|
return nullValue();
|
||||||
}
|
}
|
||||||
|
@ -215,10 +221,22 @@ public final class JsonTreeWriter extends JsonWriter {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void flush() throws IOException {
|
@Override
|
||||||
|
public JsonWriter nullValue() throws IOException {
|
||||||
|
put(JsonNull.INSTANCE);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void close() throws IOException {
|
@Override
|
||||||
|
public JsonWriter jsonValue(String value) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
if (!stack.isEmpty()) {
|
if (!stack.isEmpty()) {
|
||||||
throw new IOException("Incomplete document");
|
throw new IOException("Incomplete document");
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,43 +42,46 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>)
|
* Writes a JSON (<a href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>) encoded value to a
|
||||||
* encoded value to a stream, one token at a time. The stream includes both
|
* stream, one token at a time. The stream includes both literal values (strings, numbers, booleans
|
||||||
* literal values (strings, numbers, booleans and nulls) as well as the begin
|
* and nulls) as well as the begin and end delimiters of objects and arrays.
|
||||||
* and end delimiters of objects and arrays.
|
|
||||||
*
|
*
|
||||||
* <h2>Encoding JSON</h2>
|
* <h2>Encoding JSON</h2>
|
||||||
* To encode your data as JSON, create a new {@code JsonWriter}. Call methods
|
*
|
||||||
* on the writer as you walk the structure's contents, nesting arrays and objects
|
* To encode your data as JSON, create a new {@code JsonWriter}. Call methods on the writer as you
|
||||||
* as necessary:
|
* walk the structure's contents, nesting arrays and objects as necessary:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
|
* <li>To write <strong>arrays</strong>, first call {@link #beginArray()}. Write each of the
|
||||||
* Write each of the array's elements with the appropriate {@link #value}
|
* array's elements with the appropriate {@link #value} methods or by nesting other arrays and
|
||||||
* methods or by nesting other arrays and objects. Finally close the array
|
* objects. Finally close the array using {@link #endArray()}.
|
||||||
* using {@link #endArray()}.
|
* <li>To write <strong>objects</strong>, first call {@link #beginObject()}. Write each of the
|
||||||
* <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
|
* object's properties by alternating calls to {@link #name} with the property's value. Write
|
||||||
* Write each of the object's properties by alternating calls to
|
* property values with the appropriate {@link #value} method or by nesting other objects or
|
||||||
* {@link #name} with the property's value. Write property values with the
|
* arrays. Finally close the object using {@link #endObject()}.
|
||||||
* appropriate {@link #value} method or by nesting other objects or arrays.
|
|
||||||
* Finally close the object using {@link #endObject()}.
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h2>Configuration</h2>
|
* <h2>Configuration</h2>
|
||||||
|
*
|
||||||
* The behavior of this writer can be customized with the following methods:
|
* The behavior of this writer can be customized with the following methods:
|
||||||
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #setFormattingStyle(FormattingStyle)}, the default is {@link FormattingStyle#COMPACT}
|
* <li>{@link #setFormattingStyle(FormattingStyle)}, the default is {@link
|
||||||
* <li>{@link #setHtmlSafe(boolean)}, by default HTML characters are not escaped
|
* FormattingStyle#COMPACT}
|
||||||
* in the JSON output
|
* <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 #setStrictness(Strictness)}, the default is {@link Strictness#LEGACY_STRICT}
|
||||||
* <li>{@link #setSerializeNulls(boolean)}, by default {@code null} is serialized
|
* <li>{@link #setSerializeNulls(boolean)}, by default {@code null} is serialized
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* The default configuration of {@code JsonWriter} instances used internally by
|
* The default configuration of {@code JsonWriter} instances used internally by the {@link Gson}
|
||||||
* the {@link Gson} class differs, and can be adjusted with the various
|
* class differs, and can be adjusted with the various {@link GsonBuilder} methods.
|
||||||
* {@link GsonBuilder} methods.
|
|
||||||
*
|
*
|
||||||
* <h2>Example</h2>
|
* <h2>Example</h2>
|
||||||
* Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
|
*
|
||||||
|
* Suppose we'd like to encode a stream of messages such as the following:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
* [
|
* [
|
||||||
* {
|
* {
|
||||||
* "id": 912345678901,
|
* "id": 912345678901,
|
||||||
|
@ -98,56 +101,61 @@ import java.util.regex.Pattern;
|
||||||
* "followers_count": 2
|
* "followers_count": 2
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* ]}</pre>
|
* ]
|
||||||
* This code encodes the above structure: <pre> {@code
|
* }</pre>
|
||||||
* public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
|
*
|
||||||
* JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
|
* This code encodes the above structure:
|
||||||
* writer.setIndent(" ");
|
*
|
||||||
* writeMessagesArray(writer, messages);
|
* <pre>{@code
|
||||||
* writer.close();
|
* public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
|
||||||
|
* JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||||
|
* writer.setIndent(" ");
|
||||||
|
* writeMessagesArray(writer, messages);
|
||||||
|
* writer.close();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
|
||||||
|
* writer.beginArray();
|
||||||
|
* for (Message message : messages) {
|
||||||
|
* writeMessage(writer, message);
|
||||||
* }
|
* }
|
||||||
|
* writer.endArray();
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
* public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
|
* public void writeMessage(JsonWriter writer, Message message) throws IOException {
|
||||||
* writer.beginArray();
|
* writer.beginObject();
|
||||||
* for (Message message : messages) {
|
* writer.name("id").value(message.getId());
|
||||||
* writeMessage(writer, message);
|
* writer.name("text").value(message.getText());
|
||||||
* }
|
* if (message.getGeo() != null) {
|
||||||
* writer.endArray();
|
* writer.name("geo");
|
||||||
|
* writeDoublesArray(writer, message.getGeo());
|
||||||
|
* } else {
|
||||||
|
* writer.name("geo").nullValue();
|
||||||
* }
|
* }
|
||||||
|
* writer.name("user");
|
||||||
|
* writeUser(writer, message.getUser());
|
||||||
|
* writer.endObject();
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
* public void writeMessage(JsonWriter writer, Message message) throws IOException {
|
* public void writeUser(JsonWriter writer, User user) throws IOException {
|
||||||
* writer.beginObject();
|
* writer.beginObject();
|
||||||
* writer.name("id").value(message.getId());
|
* writer.name("name").value(user.getName());
|
||||||
* writer.name("text").value(message.getText());
|
* writer.name("followers_count").value(user.getFollowersCount());
|
||||||
* if (message.getGeo() != null) {
|
* writer.endObject();
|
||||||
* writer.name("geo");
|
* }
|
||||||
* writeDoublesArray(writer, message.getGeo());
|
*
|
||||||
* } else {
|
* public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
|
||||||
* writer.name("geo").nullValue();
|
* writer.beginArray();
|
||||||
* }
|
* for (Double value : doubles) {
|
||||||
* writer.name("user");
|
* writer.value(value);
|
||||||
* writeUser(writer, message.getUser());
|
|
||||||
* writer.endObject();
|
|
||||||
* }
|
* }
|
||||||
|
* writer.endArray();
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
*
|
*
|
||||||
* public void writeUser(JsonWriter writer, User user) throws IOException {
|
* <p>Each {@code JsonWriter} may be used to write a single JSON stream. Instances of this class are
|
||||||
* writer.beginObject();
|
* not thread safe. Calls that would result in a malformed JSON string will fail with an {@link
|
||||||
* writer.name("name").value(user.getName());
|
* IllegalStateException}.
|
||||||
* writer.name("followers_count").value(user.getFollowersCount());
|
|
||||||
* writer.endObject();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
|
|
||||||
* writer.beginArray();
|
|
||||||
* for (Double value : doubles) {
|
|
||||||
* writer.value(value);
|
|
||||||
* }
|
|
||||||
* writer.endArray();
|
|
||||||
* }}</pre>
|
|
||||||
*
|
|
||||||
* <p>Each {@code JsonWriter} may be used to write a single JSON stream.
|
|
||||||
* Instances of this class are not thread safe. Calls that would result in a
|
|
||||||
* malformed JSON string will fail with an {@link IllegalStateException}.
|
|
||||||
*
|
*
|
||||||
* @author Jesse Wilson
|
* @author Jesse Wilson
|
||||||
* @since 1.6
|
* @since 1.6
|
||||||
|
@ -155,7 +163,8 @@ import java.util.regex.Pattern;
|
||||||
public class JsonWriter implements Closeable, Flushable {
|
public class JsonWriter implements Closeable, Flushable {
|
||||||
|
|
||||||
// Syntax as defined by https://datatracker.ietf.org/doc/html/rfc8259#section-6
|
// Syntax as defined by https://datatracker.ietf.org/doc/html/rfc8259#section-6
|
||||||
private static final Pattern VALID_JSON_NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
|
private static final Pattern VALID_JSON_NUMBER_PATTERN =
|
||||||
|
Pattern.compile("-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][-+]?[0-9]+)?");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* From RFC 8259, "All Unicode characters may be placed within the
|
* From RFC 8259, "All Unicode characters may be placed within the
|
||||||
|
@ -169,6 +178,7 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
*/
|
*/
|
||||||
private static final String[] REPLACEMENT_CHARS;
|
private static final String[] REPLACEMENT_CHARS;
|
||||||
private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
|
private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
REPLACEMENT_CHARS = new String[128];
|
REPLACEMENT_CHARS = new String[128];
|
||||||
for (int i = 0; i <= 0x1f; i++) {
|
for (int i = 0; i <= 0x1f; i++) {
|
||||||
|
@ -194,6 +204,7 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
|
|
||||||
private int[] stack = new int[32];
|
private int[] stack = new int[32];
|
||||||
private int stackSize = 0;
|
private int stackSize = 0;
|
||||||
|
|
||||||
{
|
{
|
||||||
push(EMPTY_DOCUMENT);
|
push(EMPTY_DOCUMENT);
|
||||||
}
|
}
|
||||||
|
@ -220,9 +231,9 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
private List<String> deferredComment = new LinkedList<>();
|
private List<String> deferredComment = new LinkedList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance that writes a JSON-encoded stream to {@code out}.
|
* Creates a new instance that writes a JSON-encoded stream to {@code out}. For best performance,
|
||||||
* For best performance, ensure {@link Writer} is buffered; wrapping in
|
* ensure {@link Writer} is buffered; wrapping in {@link java.io.BufferedWriter BufferedWriter} if
|
||||||
* {@link java.io.BufferedWriter BufferedWriter} if necessary.
|
* necessary.
|
||||||
*/
|
*/
|
||||||
public JsonWriter(Writer out) {
|
public JsonWriter(Writer out) {
|
||||||
this.out = Objects.requireNonNull(out, "out == null");
|
this.out = Objects.requireNonNull(out, "out == null");
|
||||||
|
@ -230,16 +241,14 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the indentation string to be repeated for each level of indentation
|
* Sets the indentation string to be repeated for each level of indentation in the encoded
|
||||||
* in the encoded document. If {@code indent.isEmpty()} the encoded document
|
* document. If {@code indent.isEmpty()} the encoded document will be compact. Otherwise the
|
||||||
* will be compact. Otherwise the encoded document will be more
|
* encoded document will be more human-readable.
|
||||||
* human-readable.
|
|
||||||
*
|
*
|
||||||
* <p>This is a convenience method which overwrites any previously
|
* <p>This is a convenience method which overwrites any previously {@linkplain
|
||||||
* {@linkplain #setFormattingStyle(FormattingStyle) set formatting style} with
|
* #setFormattingStyle(FormattingStyle) set formatting style} with either {@link
|
||||||
* either {@link FormattingStyle#COMPACT} if the given indent string is
|
* FormattingStyle#COMPACT} if the given indent string is empty, or {@link FormattingStyle#PRETTY}
|
||||||
* empty, or {@link FormattingStyle#PRETTY} with the given indent if
|
* with the given indent if not empty.
|
||||||
* not empty.
|
|
||||||
*
|
*
|
||||||
* @param indent a string containing only whitespace.
|
* @param indent a string containing only whitespace.
|
||||||
*/
|
*/
|
||||||
|
@ -254,9 +263,8 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
/**
|
/**
|
||||||
* Sets the formatting style to be used in the encoded document.
|
* Sets the formatting style to be used in the encoded document.
|
||||||
*
|
*
|
||||||
* <p>The formatting style specifies for example the indentation string to be
|
* <p>The formatting style specifies for example the indentation string to be repeated for each
|
||||||
* repeated for each level of indentation, or the newline style, to accommodate
|
* level of indentation, or the newline style, to accommodate various OS styles.
|
||||||
* various OS styles.</p>
|
|
||||||
*
|
*
|
||||||
* @param formattingStyle the formatting style to use, must not be {@code null}.
|
* @param formattingStyle the formatting style to use, must not be {@code null}.
|
||||||
* @since $next-version$
|
* @since $next-version$
|
||||||
|
@ -276,8 +284,8 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
this.formattedColon = ":";
|
this.formattedColon = ":";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.usesEmptyNewlineAndIndent = this.formattingStyle.getNewline().isEmpty()
|
this.usesEmptyNewlineAndIndent =
|
||||||
&& this.formattingStyle.getIndent().isEmpty();
|
this.formattingStyle.getNewline().isEmpty() && this.formattingStyle.getIndent().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -293,17 +301,20 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
/**
|
/**
|
||||||
* Sets the strictness of this writer.
|
* Sets the strictness of this writer.
|
||||||
*
|
*
|
||||||
* @deprecated Please use {@link #setStrictness(Strictness)} instead.
|
* @deprecated Please use {@link #setStrictness(Strictness)} instead. {@code
|
||||||
* {@code JsonWriter.setLenient(true)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LENIENT)}
|
* JsonWriter.setLenient(true)} should be replaced by {@code
|
||||||
* and {@code JsonWriter.setLenient(false)} should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br>
|
* JsonWriter.setStrictness(Strictness.LENIENT)} and {@code JsonWriter.setLenient(false)}
|
||||||
* However, if you used {@code setLenient(false)} before, you might prefer {@link Strictness#STRICT} now instead.
|
* should be replaced by {@code JsonWriter.setStrictness(Strictness.LEGACY_STRICT)}.<br>
|
||||||
*
|
* However, if you used {@code setLenient(false)} before, you might prefer {@link
|
||||||
* @param lenient whether this writer should be lenient. If true, the strictness is set to {@link Strictness#LENIENT}.
|
* Strictness#STRICT} now instead.
|
||||||
* If false, the strictness is set to {@link Strictness#LEGACY_STRICT}.
|
* @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)
|
* @see #setStrictness(Strictness)
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@SuppressWarnings("InlineMeSuggester") // Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new Strictness.STRICT
|
// Don't specify @InlineMe, so caller with `setLenient(false)` becomes aware of new
|
||||||
|
// Strictness.STRICT
|
||||||
|
@SuppressWarnings("InlineMeSuggester")
|
||||||
public final void setLenient(boolean lenient) {
|
public final void setLenient(boolean lenient) {
|
||||||
setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT);
|
setStrictness(lenient ? Strictness.LENIENT : Strictness.LEGACY_STRICT);
|
||||||
if (lenient) this.serializeSpecialFloatingPointValues = true;
|
if (lenient) this.serializeSpecialFloatingPointValues = true;
|
||||||
|
@ -320,19 +331,17 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures how strict this writer is with regard to the syntax rules specified in <a
|
* 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.
|
* href="https://www.ietf.org/rfc/rfc8259.txt">RFC 8259</a>. By default, {@link
|
||||||
|
* Strictness#LEGACY_STRICT} is used.
|
||||||
*
|
*
|
||||||
* <dl>
|
* <dl>
|
||||||
* <dt>{@link Strictness#STRICT} & {@link Strictness#LEGACY_STRICT}</dt>
|
* <dt>{@link Strictness#STRICT} & {@link Strictness#LEGACY_STRICT}
|
||||||
* <dd>
|
* <dd>The behavior of these is currently identical. In these strictness modes, the writer only
|
||||||
* The behavior of these is currently identical. In these strictness modes, the writer only writes JSON
|
* writes JSON in accordance with RFC 8259.
|
||||||
* in accordance with RFC 8259.
|
* <dt>{@link Strictness#LENIENT}
|
||||||
* </dd>
|
* <dd>This mode relaxes the behavior of the writer to allow the writing of {@link
|
||||||
* <dt>{@link Strictness#LENIENT}</dt>
|
* Double#isNaN() NaNs} and {@link Double#isInfinite() infinities}. It also allows writing
|
||||||
* <dd>
|
* multiple top level values.
|
||||||
* 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>
|
* </dl>
|
||||||
*
|
*
|
||||||
* @param strictness the new strictness of this writer. May not be {@code null}.
|
* @param strictness the new strictness of this writer. May not be {@code null}.
|
||||||
|
@ -362,35 +371,33 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures this writer to emit JSON that's safe for direct inclusion in HTML
|
* Configures this writer to emit JSON that's safe for direct inclusion in HTML and XML documents.
|
||||||
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
|
* This escapes the HTML characters {@code <}, {@code >}, {@code &}, {@code =} and {@code '}
|
||||||
* {@code &} and {@code =} before writing them to the stream. Without this
|
* before writing them to the stream. Without this setting, your XML/HTML encoder should replace
|
||||||
* setting, your XML/HTML encoder should replace these characters with the
|
* these characters with the corresponding escape sequences.
|
||||||
* corresponding escape sequences.
|
|
||||||
*/
|
*/
|
||||||
public final void setHtmlSafe(boolean htmlSafe) {
|
public final void setHtmlSafe(boolean htmlSafe) {
|
||||||
this.htmlSafe = htmlSafe;
|
this.htmlSafe = htmlSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this writer writes JSON that's safe for inclusion in HTML
|
* Returns true if this writer writes JSON that's safe for inclusion in HTML and XML documents.
|
||||||
* and XML documents.
|
|
||||||
*/
|
*/
|
||||||
public final boolean isHtmlSafe() {
|
public final boolean isHtmlSafe() {
|
||||||
return htmlSafe;
|
return htmlSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether object members are serialized when their value is null.
|
* Sets whether object members are serialized when their value is null. This has no impact on
|
||||||
* This has no impact on array elements. The default is true.
|
* array elements. The default is true.
|
||||||
*/
|
*/
|
||||||
public final void setSerializeNulls(boolean serializeNulls) {
|
public final void setSerializeNulls(boolean serializeNulls) {
|
||||||
this.serializeNulls = serializeNulls;
|
this.serializeNulls = serializeNulls;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if object members are serialized when their value is null.
|
* Returns true if object members are serialized when their value is null. This has no impact on
|
||||||
* This has no impact on array elements. The default is true.
|
* array elements. The default is true.
|
||||||
*/
|
*/
|
||||||
public final boolean getSerializeNulls() {
|
public final boolean getSerializeNulls() {
|
||||||
return serializeNulls;
|
return serializeNulls;
|
||||||
|
@ -398,25 +405,29 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether serialized entry names may omit quotes (like in json5)
|
* Sets whether serialized entry names may omit quotes (like in json5)
|
||||||
* The default is false
|
* The default is false.
|
||||||
*/
|
*/
|
||||||
public final void setOmitQuotes(boolean omitQuotes) {
|
public final void setOmitQuotes(boolean omitQuotes) {
|
||||||
this.omitQuotes = omitQuotes;
|
this.omitQuotes = omitQuotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true serialized entry names may omit quotes (like in json5)
|
||||||
|
* The default is false.
|
||||||
|
*/
|
||||||
public final boolean getOmitQuotes() {
|
public final boolean getOmitQuotes() {
|
||||||
return omitQuotes;
|
return omitQuotes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins encoding a new array. Each call to this method must be paired with
|
* Begins encoding a new array. Each call to this method must be paired with a call to {@link
|
||||||
* a call to {@link #endArray}.
|
* #endArray}.
|
||||||
*
|
*
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
public JsonWriter beginArray() throws IOException {
|
public JsonWriter beginArray() throws IOException {
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
return open(EMPTY_ARRAY, '[');
|
return openScope(EMPTY_ARRAY, '[');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -425,18 +436,18 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
public JsonWriter endArray() throws IOException {
|
public JsonWriter endArray() throws IOException {
|
||||||
return close(EMPTY_ARRAY, NONEMPTY_ARRAY, ']');
|
return closeScope(EMPTY_ARRAY, NONEMPTY_ARRAY, ']');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins encoding a new object. Each call to this method must be paired
|
* Begins encoding a new object. Each call to this method must be paired with a call to {@link
|
||||||
* with a call to {@link #endObject}.
|
* #endObject}.
|
||||||
*
|
*
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
public JsonWriter beginObject() throws IOException {
|
public JsonWriter beginObject() throws IOException {
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
return open(EMPTY_OBJECT, '{');
|
return openScope(EMPTY_OBJECT, '{');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -445,26 +456,19 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
public JsonWriter endObject() throws IOException {
|
public JsonWriter endObject() throws IOException {
|
||||||
return close(EMPTY_OBJECT, NONEMPTY_OBJECT, '}');
|
return closeScope(EMPTY_OBJECT, NONEMPTY_OBJECT, '}');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Enters a new scope by appending any necessary whitespace and the given bracket. */
|
||||||
* Enters a new scope by appending any necessary whitespace and the given
|
private JsonWriter openScope(int empty, char openBracket) throws IOException {
|
||||||
* bracket.
|
|
||||||
*/
|
|
||||||
private JsonWriter open(int empty, char openBracket) throws IOException {
|
|
||||||
beforeValue();
|
beforeValue();
|
||||||
push(empty);
|
push(empty);
|
||||||
out.write(openBracket);
|
out.write(openBracket);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Closes the current scope by appending any necessary whitespace and the given bracket. */
|
||||||
* Closes the current scope by appending any necessary whitespace and the
|
private JsonWriter closeScope(int empty, int nonempty, char closeBracket) throws IOException {
|
||||||
* given bracket.
|
|
||||||
*/
|
|
||||||
private JsonWriter close(int empty, int nonempty, char closeBracket)
|
|
||||||
throws IOException {
|
|
||||||
int context = peek();
|
int context = peek();
|
||||||
if (context != nonempty && context != empty) {
|
if (context != nonempty && context != empty) {
|
||||||
throw new IllegalStateException("Nesting problem.");
|
throw new IllegalStateException("Nesting problem.");
|
||||||
|
@ -494,9 +498,7 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
stack[stackSize++] = newTop;
|
stack[stackSize++] = newTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Returns the value on the top of the stack. */
|
||||||
* Returns the value on the top of the stack.
|
|
||||||
*/
|
|
||||||
private int peek() {
|
private int peek() {
|
||||||
if (stackSize == 0) {
|
if (stackSize == 0) {
|
||||||
throw new IllegalStateException("JsonWriter is closed.");
|
throw new IllegalStateException("JsonWriter is closed.");
|
||||||
|
@ -504,9 +506,7 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
return stack[stackSize - 1];
|
return stack[stackSize - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Replace the value on the top of the stack with the given value. */
|
||||||
* Replace the value on the top of the stack with the given value.
|
|
||||||
*/
|
|
||||||
private void replaceTop(int topOfStack) {
|
private void replaceTop(int topOfStack) {
|
||||||
stack[stackSize - 1] = topOfStack;
|
stack[stackSize - 1] = topOfStack;
|
||||||
}
|
}
|
||||||
|
@ -514,7 +514,7 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
/**
|
/**
|
||||||
* Insert a comment at the current location.
|
* Insert a comment at the current location.
|
||||||
* May create a new line.
|
* May create a new line.
|
||||||
* This writer MUST be lenient to use this
|
* This writer MUST be lenient to use this.
|
||||||
*/
|
*/
|
||||||
public JsonWriter comment(String comment) throws IOException {
|
public JsonWriter comment(String comment) throws IOException {
|
||||||
if (strictness != Strictness.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.");
|
||||||
|
@ -592,46 +592,6 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes {@code value} directly to the writer without quoting or
|
|
||||||
* escaping. This might not be supported by all implementations, if
|
|
||||||
* not supported an {@code UnsupportedOperationException} is thrown.
|
|
||||||
*
|
|
||||||
* @param value the literal string value, or null to encode a null literal.
|
|
||||||
* @return this writer.
|
|
||||||
* @throws UnsupportedOperationException if this writer does not support
|
|
||||||
* writing raw JSON values.
|
|
||||||
* @since 2.4
|
|
||||||
*/
|
|
||||||
public JsonWriter jsonValue(String value) throws IOException {
|
|
||||||
if (value == null) {
|
|
||||||
return nullValue();
|
|
||||||
}
|
|
||||||
writeDeferredName();
|
|
||||||
beforeValue();
|
|
||||||
out.append(value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes {@code null}.
|
|
||||||
*
|
|
||||||
* @return this writer.
|
|
||||||
*/
|
|
||||||
public JsonWriter nullValue() throws IOException {
|
|
||||||
if (deferredName != null) {
|
|
||||||
if (serializeNulls) {
|
|
||||||
writeDeferredName();
|
|
||||||
} else {
|
|
||||||
deferredName = null;
|
|
||||||
return this; // skip the name and the value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
beforeValue();
|
|
||||||
out.write("null");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes {@code value}.
|
* Encodes {@code value}.
|
||||||
*
|
*
|
||||||
|
@ -663,9 +623,8 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
/**
|
/**
|
||||||
* Encodes {@code value}.
|
* Encodes {@code value}.
|
||||||
*
|
*
|
||||||
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
|
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, also {@link
|
||||||
* also {@link Float#isNaN() NaN} or {@link Float#isInfinite()
|
* Float#isNaN() NaN} or {@link Float#isInfinite() infinity}.
|
||||||
* infinity}.
|
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
|
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
|
||||||
* #setStrictness(Strictness) lenient}.
|
* #setStrictness(Strictness) lenient}.
|
||||||
|
@ -684,11 +643,11 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
/**
|
/**
|
||||||
* Encodes {@code value}.
|
* Encodes {@code value}.
|
||||||
*
|
*
|
||||||
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
|
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, also {@link
|
||||||
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
|
* Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
|
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
|
||||||
* not {@link #setStrictness(Strictness) lenient}.
|
* #setStrictness(Strictness) lenient}.
|
||||||
*/
|
*/
|
||||||
public JsonWriter value(double value) throws IOException {
|
public JsonWriter value(double value) throws IOException {
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
|
@ -712,27 +671,16 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the {@code toString()} of {@code c} can be trusted to return
|
|
||||||
* a valid JSON number.
|
|
||||||
*/
|
|
||||||
private static boolean isTrustedNumberType(Class<? extends Number> c) {
|
|
||||||
// Note: Don't consider LazilyParsedNumber trusted because it could contain
|
|
||||||
// an arbitrary malformed string
|
|
||||||
return c == Integer.class || c == Long.class || c == Double.class || c == Float.class || c == Byte.class || c == Short.class
|
|
||||||
|| c == BigDecimal.class || c == BigInteger.class || c == AtomicInteger.class || c == AtomicLong.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes {@code value}. The value is written by directly writing the {@link Number#toString()}
|
* 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.
|
* result to JSON. Implementations must make sure that the result represents a valid JSON number.
|
||||||
*
|
*
|
||||||
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient},
|
* @param value a finite value, or if {@link #setStrictness(Strictness) lenient}, also {@link
|
||||||
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
|
* Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
|
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
|
||||||
* not {@link #setStrictness(Strictness) lenient}; or if the {@code toString()} result is not a
|
* #setStrictness(Strictness) lenient}; or if the {@code toString()} result is not a valid
|
||||||
* valid JSON number.
|
* JSON number.
|
||||||
*/
|
*/
|
||||||
public JsonWriter value(Number value) throws IOException {
|
public JsonWriter value(Number value) throws IOException {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -748,8 +696,10 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
} else {
|
} else {
|
||||||
Class<? extends Number> numberClass = value.getClass();
|
Class<? extends Number> numberClass = value.getClass();
|
||||||
// Validate that string is valid before writing it directly to JSON output
|
// Validate that string is valid before writing it directly to JSON output
|
||||||
if (!isTrustedNumberType(numberClass) && !VALID_JSON_NUMBER_PATTERN.matcher(string).matches()) {
|
if (!isTrustedNumberType(numberClass)
|
||||||
throw new IllegalArgumentException("String created by " + numberClass + " is not a valid JSON number: " + string);
|
&& !VALID_JSON_NUMBER_PATTERN.matcher(string).matches()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"String created by " + numberClass + " is not a valid JSON number: " + string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -759,10 +709,49 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures all buffered data is written to the underlying {@link Writer}
|
* Encodes {@code null}.
|
||||||
* and flushes that writer.
|
*
|
||||||
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
@Override public void flush() throws IOException {
|
public JsonWriter nullValue() throws IOException {
|
||||||
|
if (deferredName != null) {
|
||||||
|
if (serializeNulls) {
|
||||||
|
writeDeferredName();
|
||||||
|
} else {
|
||||||
|
deferredName = null;
|
||||||
|
return this; // skip the name and the value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
out.write("null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes {@code value} directly to the writer without quoting or escaping. This might not be
|
||||||
|
* supported by all implementations, if not supported an {@code UnsupportedOperationException} is
|
||||||
|
* thrown.
|
||||||
|
*
|
||||||
|
* @param value the literal string value, or null to encode a null literal.
|
||||||
|
* @return this writer.
|
||||||
|
* @throws UnsupportedOperationException if this writer does not support writing raw JSON values.
|
||||||
|
* @since 2.4
|
||||||
|
*/
|
||||||
|
public JsonWriter jsonValue(String value) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
return nullValue();
|
||||||
|
}
|
||||||
|
writeDeferredName();
|
||||||
|
beforeValue();
|
||||||
|
out.append(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures all buffered data is written to the underlying {@link Writer} and flushes that writer.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
if (stackSize == 0) {
|
if (stackSize == 0) {
|
||||||
throw new IllegalStateException("JsonWriter is closed.");
|
throw new IllegalStateException("JsonWriter is closed.");
|
||||||
}
|
}
|
||||||
|
@ -774,7 +763,8 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
*
|
*
|
||||||
* @throws IOException if the JSON document is incomplete.
|
* @throws IOException if the JSON document is incomplete.
|
||||||
*/
|
*/
|
||||||
@Override public void close() throws IOException {
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
int size = stackSize;
|
int size = stackSize;
|
||||||
|
@ -784,6 +774,25 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
stackSize = 0;
|
stackSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the {@code toString()} of {@code c} can be trusted to return a valid JSON
|
||||||
|
* number.
|
||||||
|
*/
|
||||||
|
private static boolean isTrustedNumberType(Class<? extends Number> c) {
|
||||||
|
// Note: Don't consider LazilyParsedNumber trusted because it could contain
|
||||||
|
// an arbitrary malformed string
|
||||||
|
return c == Integer.class
|
||||||
|
|| c == Long.class
|
||||||
|
|| c == Double.class
|
||||||
|
|| c == Float.class
|
||||||
|
|| c == Byte.class
|
||||||
|
|| c == Short.class
|
||||||
|
|| c == BigDecimal.class
|
||||||
|
|| c == BigInteger.class
|
||||||
|
|| c == AtomicInteger.class
|
||||||
|
|| c == AtomicLong.class;
|
||||||
|
}
|
||||||
|
|
||||||
private void string(String value) throws IOException {
|
private void string(String value) throws IOException {
|
||||||
String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS;
|
String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS;
|
||||||
out.write('\"');
|
out.write('\"');
|
||||||
|
@ -828,8 +837,8 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts any necessary separators and whitespace before a name. Also
|
* Inserts any necessary separators and whitespace before a name. Also adjusts the stack to expect
|
||||||
* adjusts the stack to expect the name's value.
|
* the name's value.
|
||||||
*/
|
*/
|
||||||
private void beforeName() throws IOException {
|
private void beforeName() throws IOException {
|
||||||
int context = peek();
|
int context = peek();
|
||||||
|
@ -847,56 +856,55 @@ public class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts any necessary separators and whitespace before a literal value,
|
* Inserts any necessary separators and whitespace before a literal value, inline array, or inline
|
||||||
* inline array, or inline object. Also adjusts the stack to expect either a
|
* object. Also adjusts the stack to expect either a closing bracket or another element.
|
||||||
* closing bracket or another element.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("fallthrough")
|
@SuppressWarnings("fallthrough")
|
||||||
private void beforeValue() throws IOException {
|
private void beforeValue() throws IOException {
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case NONEMPTY_DOCUMENT:
|
case NONEMPTY_DOCUMENT:
|
||||||
if (strictness != Strictness.LENIENT) {
|
if (strictness != Strictness.LENIENT) {
|
||||||
throw new IllegalStateException("JSON must have only one top-level value.");
|
throw new IllegalStateException("JSON must have only one top-level value.");
|
||||||
}
|
}
|
||||||
// fall-through
|
// fall-through
|
||||||
case EMPTY_DOCUMENT: // first in document
|
case EMPTY_DOCUMENT: // first in document
|
||||||
replaceTop(NONEMPTY_DOCUMENT);
|
replaceTop(NONEMPTY_DOCUMENT);
|
||||||
if (!deferredComment.isEmpty()) {
|
if (!deferredComment.isEmpty()) {
|
||||||
writeDeferredComment();
|
writeDeferredComment();
|
||||||
newline();
|
newline();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EMPTY_ARRAY: // first in array
|
case EMPTY_ARRAY: // first in array
|
||||||
replaceTop(NONEMPTY_ARRAY);
|
replaceTop(NONEMPTY_ARRAY);
|
||||||
newline();
|
|
||||||
if (!deferredComment.isEmpty()) {
|
|
||||||
writeDeferredComment();
|
|
||||||
newline();
|
newline();
|
||||||
}
|
if (!deferredComment.isEmpty()) {
|
||||||
break;
|
writeDeferredComment();
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case NONEMPTY_ARRAY: // another in array
|
case NONEMPTY_ARRAY: // another in array
|
||||||
out.append(formattedComma);
|
out.append(formattedComma);
|
||||||
newline();
|
|
||||||
if (!deferredComment.isEmpty()) {
|
|
||||||
writeDeferredComment();
|
|
||||||
newline();
|
newline();
|
||||||
}
|
if (!deferredComment.isEmpty()) {
|
||||||
break;
|
writeDeferredComment();
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case DANGLING_NAME: // value for name
|
case DANGLING_NAME: // value for name
|
||||||
out.append(formattedColon);
|
out.append(formattedColon);
|
||||||
if (!deferredComment.isEmpty()) {
|
if (!deferredComment.isEmpty()) {
|
||||||
newline();
|
newline();
|
||||||
writeDeferredComment();
|
writeDeferredComment();
|
||||||
newline();
|
newline();
|
||||||
}
|
}
|
||||||
replaceTop(NONEMPTY_OBJECT);
|
replaceTop(NONEMPTY_OBJECT);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Nesting problem.");
|
throw new IllegalStateException("Nesting problem.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ import com.google.gson.Strictness;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a reader encounters malformed JSON. Some syntax errors can be
|
* Thrown when a reader encounters malformed JSON. Some syntax errors can be ignored by using {@link
|
||||||
* ignored by using {@link Strictness#LENIENT} for {@link JsonReader#setStrictness(Strictness)}.
|
* Strictness#LENIENT} for {@link JsonReader#setStrictness(Strictness)}.
|
||||||
*/
|
*/
|
||||||
public final class MalformedJsonException extends IOException {
|
public final class MalformedJsonException extends IOException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
|
@ -14,7 +14,5 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/** This package provides classes for processing JSON in an efficient streaming way. */
|
||||||
* This package provides classes for processing JSON in an efficient streaming way.
|
|
||||||
*/
|
|
||||||
package com.google.gson.stream;
|
package com.google.gson.stream;
|
||||||
|
|
|
@ -25,349 +25,361 @@ import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities methods for manipulating dates in iso8601 format. This is much faster and GC friendly than using SimpleDateFormat so
|
* Utilities methods for manipulating dates in iso8601 format. This is much faster and GC friendly
|
||||||
* highly suitable if you (un)serialize lots of date objects.
|
* 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]]
|
* <p>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>
|
* @see <a href="http://www.w3.org/TR/NOTE-datetime">this specification</a>
|
||||||
*/
|
*/
|
||||||
// Date parsing code from Jackson databind 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
|
// https://github.com/FasterXML/jackson-databind/blob/2.8/src/main/java/com/fasterxml/jackson/databind/util/ISO8601Utils.java
|
||||||
public class ISO8601Utils
|
public class ISO8601Utils {
|
||||||
{
|
private ISO8601Utils() {}
|
||||||
/**
|
|
||||||
* ID to represent the 'UTC' string, default timezone since Jackson 2.7
|
|
||||||
*
|
|
||||||
* @since 2.7
|
|
||||||
*/
|
|
||||||
private static final String UTC_ID = "UTC";
|
|
||||||
/**
|
|
||||||
* The UTC timezone, prefetched to avoid more lookups.
|
|
||||||
*
|
|
||||||
* @since 2.7
|
|
||||||
*/
|
|
||||||
private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone(UTC_ID);
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
/**********************************************************
|
* ID to represent the 'UTC' string, default timezone since Jackson 2.7
|
||||||
/* Formatting
|
*
|
||||||
/**********************************************************
|
* @since 2.7
|
||||||
*/
|
*/
|
||||||
|
private static final String UTC_ID = "UTC";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a date into 'yyyy-MM-ddThh:mm:ssZ' (default timezone, no milliseconds precision)
|
* The UTC timezone, prefetched to avoid more lookups.
|
||||||
*
|
*
|
||||||
* @param date the date to format
|
* @since 2.7
|
||||||
* @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ'
|
*/
|
||||||
*/
|
private static final TimeZone TIMEZONE_UTC = TimeZone.getTimeZone(UTC_ID);
|
||||||
public static String format(Date date) {
|
|
||||||
return format(date, false, TIMEZONE_UTC);
|
/*
|
||||||
|
/**********************************************************
|
||||||
|
/* Formatting
|
||||||
|
/**********************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date into 'yyyy-MM-ddThh:mm:ssZ' (default timezone, no milliseconds precision)
|
||||||
|
*
|
||||||
|
* @param date the date to format
|
||||||
|
* @return the date formatted as 'yyyy-MM-ddThh:mm:ssZ'
|
||||||
|
*/
|
||||||
|
public static String format(Date date) {
|
||||||
|
return format(date, false, TIMEZONE_UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date into 'yyyy-MM-ddThh:mm:ss[.sss]Z' (GMT timezone)
|
||||||
|
*
|
||||||
|
* @param date the date to format
|
||||||
|
* @param millis true to include millis precision otherwise false
|
||||||
|
* @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z'
|
||||||
|
*/
|
||||||
|
public static String format(Date date, boolean millis) {
|
||||||
|
return format(date, millis, TIMEZONE_UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format date into yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
|
||||||
|
*
|
||||||
|
* @param date the date to format
|
||||||
|
* @param millis true to include millis precision otherwise false
|
||||||
|
* @param tz timezone to use for the formatting (UTC will produce 'Z')
|
||||||
|
* @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
|
||||||
|
*/
|
||||||
|
public static String format(Date date, boolean millis, TimeZone tz) {
|
||||||
|
Calendar calendar = new GregorianCalendar(tz, Locale.US);
|
||||||
|
calendar.setTime(date);
|
||||||
|
|
||||||
|
// estimate capacity of buffer as close as we can (yeah, that's pedantic ;)
|
||||||
|
int capacity = "yyyy-MM-ddThh:mm:ss".length();
|
||||||
|
capacity += millis ? ".sss".length() : 0;
|
||||||
|
capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length();
|
||||||
|
StringBuilder formatted = new StringBuilder(capacity);
|
||||||
|
|
||||||
|
padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length());
|
||||||
|
formatted.append('-');
|
||||||
|
padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length());
|
||||||
|
formatted.append('-');
|
||||||
|
padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length());
|
||||||
|
formatted.append('T');
|
||||||
|
padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length());
|
||||||
|
formatted.append(':');
|
||||||
|
padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length());
|
||||||
|
formatted.append(':');
|
||||||
|
padInt(formatted, calendar.get(Calendar.SECOND), "ss".length());
|
||||||
|
if (millis) {
|
||||||
|
formatted.append('.');
|
||||||
|
padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
int offset = tz.getOffset(calendar.getTimeInMillis());
|
||||||
* Format a date into 'yyyy-MM-ddThh:mm:ss[.sss]Z' (GMT timezone)
|
if (offset != 0) {
|
||||||
*
|
int hours = Math.abs((offset / (60 * 1000)) / 60);
|
||||||
* @param date the date to format
|
int minutes = Math.abs((offset / (60 * 1000)) % 60);
|
||||||
* @param millis true to include millis precision otherwise false
|
formatted.append(offset < 0 ? '-' : '+');
|
||||||
* @return the date formatted as 'yyyy-MM-ddThh:mm:ss[.sss]Z'
|
padInt(formatted, hours, "hh".length());
|
||||||
*/
|
formatted.append(':');
|
||||||
public static String format(Date date, boolean millis) {
|
padInt(formatted, minutes, "mm".length());
|
||||||
return format(date, millis, TIMEZONE_UTC);
|
} else {
|
||||||
|
formatted.append('Z');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return formatted.toString();
|
||||||
* Format date into yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
|
}
|
||||||
*
|
|
||||||
* @param date the date to format
|
|
||||||
* @param millis true to include millis precision otherwise false
|
|
||||||
* @param tz timezone to use for the formatting (UTC will produce 'Z')
|
|
||||||
* @return the date formatted as yyyy-MM-ddThh:mm:ss[.sss][Z|[+-]hh:mm]
|
|
||||||
*/
|
|
||||||
public static String format(Date date, boolean millis, TimeZone tz) {
|
|
||||||
Calendar calendar = new GregorianCalendar(tz, Locale.US);
|
|
||||||
calendar.setTime(date);
|
|
||||||
|
|
||||||
// estimate capacity of buffer as close as we can (yeah, that's pedantic ;)
|
/*
|
||||||
int capacity = "yyyy-MM-ddThh:mm:ss".length();
|
/**********************************************************
|
||||||
capacity += millis ? ".sss".length() : 0;
|
/* Parsing
|
||||||
capacity += tz.getRawOffset() == 0 ? "Z".length() : "+hh:mm".length();
|
/**********************************************************
|
||||||
StringBuilder formatted = new StringBuilder(capacity);
|
*/
|
||||||
|
|
||||||
padInt(formatted, calendar.get(Calendar.YEAR), "yyyy".length());
|
/**
|
||||||
formatted.append('-');
|
* Parse a date from ISO-8601 formatted string. It expects a format
|
||||||
padInt(formatted, calendar.get(Calendar.MONTH) + 1, "MM".length());
|
* [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:mm]]]
|
||||||
formatted.append('-');
|
*
|
||||||
padInt(formatted, calendar.get(Calendar.DAY_OF_MONTH), "dd".length());
|
* @param date ISO string to parse in the appropriate format.
|
||||||
formatted.append('T');
|
* @param pos The position to start parsing from, updated to where parsing stopped.
|
||||||
padInt(formatted, calendar.get(Calendar.HOUR_OF_DAY), "hh".length());
|
* @return the parsed date
|
||||||
formatted.append(':');
|
* @throws ParseException if the date is not in the appropriate format
|
||||||
padInt(formatted, calendar.get(Calendar.MINUTE), "mm".length());
|
*/
|
||||||
formatted.append(':');
|
public static Date parse(String date, ParsePosition pos) throws ParseException {
|
||||||
padInt(formatted, calendar.get(Calendar.SECOND), "ss".length());
|
Exception fail = null;
|
||||||
if (millis) {
|
try {
|
||||||
formatted.append('.');
|
int offset = pos.getIndex();
|
||||||
padInt(formatted, calendar.get(Calendar.MILLISECOND), "sss".length());
|
|
||||||
|
// extract year
|
||||||
|
int year = parseInt(date, offset, offset += 4);
|
||||||
|
if (checkOffset(date, offset, '-')) {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract month
|
||||||
|
int month = parseInt(date, offset, offset += 2);
|
||||||
|
if (checkOffset(date, offset, '-')) {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract day
|
||||||
|
int day = parseInt(date, offset, offset += 2);
|
||||||
|
|
||||||
|
// default time value
|
||||||
|
int hour = 0;
|
||||||
|
int minutes = 0;
|
||||||
|
int seconds = 0;
|
||||||
|
|
||||||
|
// always use 0 otherwise returned date will include millis of current time
|
||||||
|
int milliseconds = 0;
|
||||||
|
|
||||||
|
// if the value has no time component (and no time zone), we are done
|
||||||
|
boolean hasT = checkOffset(date, offset, 'T');
|
||||||
|
|
||||||
|
if (!hasT && (date.length() <= offset)) {
|
||||||
|
Calendar calendar = new GregorianCalendar(year, month - 1, day);
|
||||||
|
calendar.setLenient(false);
|
||||||
|
|
||||||
|
pos.setIndex(offset);
|
||||||
|
return calendar.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasT) {
|
||||||
|
|
||||||
|
// extract hours, minutes, seconds and milliseconds
|
||||||
|
hour = parseInt(date, offset += 1, offset += 2);
|
||||||
|
if (checkOffset(date, offset, ':')) {
|
||||||
|
offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = tz.getOffset(calendar.getTimeInMillis());
|
minutes = parseInt(date, offset, offset += 2);
|
||||||
if (offset != 0) {
|
if (checkOffset(date, offset, ':')) {
|
||||||
int hours = Math.abs((offset / (60 * 1000)) / 60);
|
offset += 1;
|
||||||
int minutes = Math.abs((offset / (60 * 1000)) % 60);
|
}
|
||||||
formatted.append(offset < 0 ? '-' : '+');
|
// second and milliseconds can be optional
|
||||||
padInt(formatted, hours, "hh".length());
|
if (date.length() > offset) {
|
||||||
formatted.append(':');
|
char c = date.charAt(offset);
|
||||||
padInt(formatted, minutes, "mm".length());
|
if (c != 'Z' && c != '+' && c != '-') {
|
||||||
|
seconds = parseInt(date, offset, offset += 2);
|
||||||
|
if (seconds > 59 && seconds < 63) {
|
||||||
|
seconds = 59; // truncate up to 3 leap seconds
|
||||||
|
}
|
||||||
|
// milliseconds can be optional in the format
|
||||||
|
if (checkOffset(date, offset, '.')) {
|
||||||
|
offset += 1;
|
||||||
|
int endOffset = indexOfNonDigit(date, offset + 1); // assume at least one digit
|
||||||
|
int parseEndOffset = Math.min(endOffset, offset + 3); // parse up to 3 digits
|
||||||
|
int fraction = parseInt(date, offset, parseEndOffset);
|
||||||
|
// compensate for "missing" digits
|
||||||
|
switch (parseEndOffset - offset) { // number of digits parsed
|
||||||
|
case 2:
|
||||||
|
milliseconds = fraction * 10;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
milliseconds = fraction * 100;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
milliseconds = fraction;
|
||||||
|
}
|
||||||
|
offset = endOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract timezone
|
||||||
|
if (date.length() <= offset) {
|
||||||
|
throw new IllegalArgumentException("No time zone indicator");
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeZone timezone = null;
|
||||||
|
char timezoneIndicator = date.charAt(offset);
|
||||||
|
|
||||||
|
if (timezoneIndicator == 'Z') {
|
||||||
|
timezone = TIMEZONE_UTC;
|
||||||
|
offset += 1;
|
||||||
|
} else if (timezoneIndicator == '+' || timezoneIndicator == '-') {
|
||||||
|
String timezoneOffset = date.substring(offset);
|
||||||
|
|
||||||
|
// When timezone has no minutes, we should append it, valid timezones are, for example:
|
||||||
|
// +00:00, +0000 and +00
|
||||||
|
timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + "00";
|
||||||
|
|
||||||
|
offset += timezoneOffset.length();
|
||||||
|
// 18-Jun-2015, tatu: Minor simplification, skip offset of "+0000"/"+00:00"
|
||||||
|
if (timezoneOffset.equals("+0000") || timezoneOffset.equals("+00:00")) {
|
||||||
|
timezone = TIMEZONE_UTC;
|
||||||
} else {
|
} else {
|
||||||
formatted.append('Z');
|
// 18-Jun-2015, tatu: Looks like offsets only work from GMT, not UTC...
|
||||||
}
|
// not sure why, but that's the way it looks. Further, Javadocs for
|
||||||
|
// `java.util.TimeZone` specifically instruct use of GMT as base for
|
||||||
|
// custom timezones... odd.
|
||||||
|
String timezoneId = "GMT" + timezoneOffset;
|
||||||
|
// String timezoneId = "UTC" + timezoneOffset;
|
||||||
|
|
||||||
return formatted.toString();
|
timezone = TimeZone.getTimeZone(timezoneId);
|
||||||
|
|
||||||
|
String act = timezone.getID();
|
||||||
|
if (!act.equals(timezoneId)) {
|
||||||
|
/* 22-Jan-2015, tatu: Looks like canonical version has colons, but we may be given
|
||||||
|
* one without. If so, don't sweat.
|
||||||
|
* Yes, very inefficient. Hopefully not hit often.
|
||||||
|
* If it becomes a perf problem, add 'loose' comparison instead.
|
||||||
|
*/
|
||||||
|
String cleaned = act.replace(":", "");
|
||||||
|
if (!cleaned.equals(timezoneId)) {
|
||||||
|
throw new IndexOutOfBoundsException(
|
||||||
|
"Mismatching time zone indicator: "
|
||||||
|
+ timezoneId
|
||||||
|
+ " given, resolves to "
|
||||||
|
+ timezone.getID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IndexOutOfBoundsException(
|
||||||
|
"Invalid time zone indicator '" + timezoneIndicator + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
Calendar calendar = new GregorianCalendar(timezone);
|
||||||
|
calendar.setLenient(false);
|
||||||
|
calendar.set(Calendar.YEAR, year);
|
||||||
|
calendar.set(Calendar.MONTH, month - 1);
|
||||||
|
calendar.set(Calendar.DAY_OF_MONTH, day);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
||||||
|
calendar.set(Calendar.MINUTE, minutes);
|
||||||
|
calendar.set(Calendar.SECOND, seconds);
|
||||||
|
calendar.set(Calendar.MILLISECOND, milliseconds);
|
||||||
|
|
||||||
|
pos.setIndex(offset);
|
||||||
|
return calendar.getTime();
|
||||||
|
// If we get a ParseException it'll already have the right message/offset.
|
||||||
|
// Other exception types can convert here.
|
||||||
|
} catch (IndexOutOfBoundsException | IllegalArgumentException e) {
|
||||||
|
fail = e;
|
||||||
}
|
}
|
||||||
|
String input = (date == null) ? null : ('"' + date + '"');
|
||||||
/*
|
String msg = fail.getMessage();
|
||||||
/**********************************************************
|
if (msg == null || msg.isEmpty()) {
|
||||||
/* Parsing
|
msg = "(" + fail.getClass().getName() + ")";
|
||||||
/**********************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a date from ISO-8601 formatted string. It expects a format
|
|
||||||
* [yyyy-MM-dd|yyyyMMdd][T(hh:mm[:ss[.sss]]|hhmm[ss[.sss]])]?[Z|[+-]hh[:mm]]]
|
|
||||||
*
|
|
||||||
* @param date ISO string to parse in the appropriate format.
|
|
||||||
* @param pos The position to start parsing from, updated to where parsing stopped.
|
|
||||||
* @return the parsed date
|
|
||||||
* @throws ParseException if the date is not in the appropriate format
|
|
||||||
*/
|
|
||||||
public static Date parse(String date, ParsePosition pos) throws ParseException {
|
|
||||||
Exception fail = null;
|
|
||||||
try {
|
|
||||||
int offset = pos.getIndex();
|
|
||||||
|
|
||||||
// extract year
|
|
||||||
int year = parseInt(date, offset, offset += 4);
|
|
||||||
if (checkOffset(date, offset, '-')) {
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract month
|
|
||||||
int month = parseInt(date, offset, offset += 2);
|
|
||||||
if (checkOffset(date, offset, '-')) {
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract day
|
|
||||||
int day = parseInt(date, offset, offset += 2);
|
|
||||||
// default time value
|
|
||||||
int hour = 0;
|
|
||||||
int minutes = 0;
|
|
||||||
int seconds = 0;
|
|
||||||
int milliseconds = 0; // always use 0 otherwise returned date will include millis of current time
|
|
||||||
|
|
||||||
// if the value has no time component (and no time zone), we are done
|
|
||||||
boolean hasT = checkOffset(date, offset, 'T');
|
|
||||||
|
|
||||||
if (!hasT && (date.length() <= offset)) {
|
|
||||||
Calendar calendar = new GregorianCalendar(year, month - 1, day);
|
|
||||||
calendar.setLenient(false);
|
|
||||||
|
|
||||||
pos.setIndex(offset);
|
|
||||||
return calendar.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasT) {
|
|
||||||
|
|
||||||
// extract hours, minutes, seconds and milliseconds
|
|
||||||
hour = parseInt(date, offset += 1, offset += 2);
|
|
||||||
if (checkOffset(date, offset, ':')) {
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
minutes = parseInt(date, offset, offset += 2);
|
|
||||||
if (checkOffset(date, offset, ':')) {
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
// second and milliseconds can be optional
|
|
||||||
if (date.length() > offset) {
|
|
||||||
char c = date.charAt(offset);
|
|
||||||
if (c != 'Z' && c != '+' && c != '-') {
|
|
||||||
seconds = parseInt(date, offset, offset += 2);
|
|
||||||
if (seconds > 59 && seconds < 63) seconds = 59; // truncate up to 3 leap seconds
|
|
||||||
// milliseconds can be optional in the format
|
|
||||||
if (checkOffset(date, offset, '.')) {
|
|
||||||
offset += 1;
|
|
||||||
int endOffset = indexOfNonDigit(date, offset + 1); // assume at least one digit
|
|
||||||
int parseEndOffset = Math.min(endOffset, offset + 3); // parse up to 3 digits
|
|
||||||
int fraction = parseInt(date, offset, parseEndOffset);
|
|
||||||
// compensate for "missing" digits
|
|
||||||
switch (parseEndOffset - offset) { // number of digits parsed
|
|
||||||
case 2:
|
|
||||||
milliseconds = fraction * 10;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
milliseconds = fraction * 100;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
milliseconds = fraction;
|
|
||||||
}
|
|
||||||
offset = endOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract timezone
|
|
||||||
if (date.length() <= offset) {
|
|
||||||
throw new IllegalArgumentException("No time zone indicator");
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeZone timezone = null;
|
|
||||||
char timezoneIndicator = date.charAt(offset);
|
|
||||||
|
|
||||||
if (timezoneIndicator == 'Z') {
|
|
||||||
timezone = TIMEZONE_UTC;
|
|
||||||
offset += 1;
|
|
||||||
} else if (timezoneIndicator == '+' || timezoneIndicator == '-') {
|
|
||||||
String timezoneOffset = date.substring(offset);
|
|
||||||
|
|
||||||
// When timezone has no minutes, we should append it, valid timezones are, for example: +00:00, +0000 and +00
|
|
||||||
timezoneOffset = timezoneOffset.length() >= 5 ? timezoneOffset : timezoneOffset + "00";
|
|
||||||
|
|
||||||
offset += timezoneOffset.length();
|
|
||||||
// 18-Jun-2015, tatu: Minor simplification, skip offset of "+0000"/"+00:00"
|
|
||||||
if ("+0000".equals(timezoneOffset) || "+00:00".equals(timezoneOffset)) {
|
|
||||||
timezone = TIMEZONE_UTC;
|
|
||||||
} else {
|
|
||||||
// 18-Jun-2015, tatu: Looks like offsets only work from GMT, not UTC...
|
|
||||||
// not sure why, but that's the way it looks. Further, Javadocs for
|
|
||||||
// `java.util.TimeZone` specifically instruct use of GMT as base for
|
|
||||||
// custom timezones... odd.
|
|
||||||
String timezoneId = "GMT" + timezoneOffset;
|
|
||||||
// String timezoneId = "UTC" + timezoneOffset;
|
|
||||||
|
|
||||||
timezone = TimeZone.getTimeZone(timezoneId);
|
|
||||||
|
|
||||||
String act = timezone.getID();
|
|
||||||
if (!act.equals(timezoneId)) {
|
|
||||||
/* 22-Jan-2015, tatu: Looks like canonical version has colons, but we may be given
|
|
||||||
* one without. If so, don't sweat.
|
|
||||||
* Yes, very inefficient. Hopefully not hit often.
|
|
||||||
* If it becomes a perf problem, add 'loose' comparison instead.
|
|
||||||
*/
|
|
||||||
String cleaned = act.replace(":", "");
|
|
||||||
if (!cleaned.equals(timezoneId)) {
|
|
||||||
throw new IndexOutOfBoundsException("Mismatching time zone indicator: "+timezoneId+" given, resolves to "
|
|
||||||
+timezone.getID());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new IndexOutOfBoundsException("Invalid time zone indicator '" + timezoneIndicator+"'");
|
|
||||||
}
|
|
||||||
|
|
||||||
Calendar calendar = new GregorianCalendar(timezone);
|
|
||||||
calendar.setLenient(false);
|
|
||||||
calendar.set(Calendar.YEAR, year);
|
|
||||||
calendar.set(Calendar.MONTH, month - 1);
|
|
||||||
calendar.set(Calendar.DAY_OF_MONTH, day);
|
|
||||||
calendar.set(Calendar.HOUR_OF_DAY, hour);
|
|
||||||
calendar.set(Calendar.MINUTE, minutes);
|
|
||||||
calendar.set(Calendar.SECOND, seconds);
|
|
||||||
calendar.set(Calendar.MILLISECOND, milliseconds);
|
|
||||||
|
|
||||||
pos.setIndex(offset);
|
|
||||||
return calendar.getTime();
|
|
||||||
// If we get a ParseException it'll already have the right message/offset.
|
|
||||||
// Other exception types can convert here.
|
|
||||||
} catch (IndexOutOfBoundsException e) {
|
|
||||||
fail = e;
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
fail = e;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
fail = e;
|
|
||||||
}
|
|
||||||
String input = (date == null) ? null : ('"' + date + '"');
|
|
||||||
String msg = fail.getMessage();
|
|
||||||
if (msg == null || msg.isEmpty()) {
|
|
||||||
msg = "("+fail.getClass().getName()+")";
|
|
||||||
}
|
|
||||||
ParseException ex = new ParseException("Failed to parse date [" + input + "]: " + msg, pos.getIndex());
|
|
||||||
ex.initCause(fail);
|
|
||||||
throw ex;
|
|
||||||
}
|
}
|
||||||
|
ParseException ex =
|
||||||
|
new ParseException("Failed to parse date [" + input + "]: " + msg, pos.getIndex());
|
||||||
|
ex.initCause(fail);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the expected character exist at the given offset in the value.
|
* Check if the expected character exist at the given offset in the value.
|
||||||
*
|
*
|
||||||
* @param value the string to check at the specified offset
|
* @param value the string to check at the specified offset
|
||||||
* @param offset the offset to look for the expected character
|
* @param offset the offset to look for the expected character
|
||||||
* @param expected the expected character
|
* @param expected the expected character
|
||||||
* @return true if the expected character exist at the given offset
|
* @return true if the expected character exist at the given offset
|
||||||
*/
|
*/
|
||||||
private static boolean checkOffset(String value, int offset, char expected) {
|
private static boolean checkOffset(String value, int offset, char expected) {
|
||||||
return (offset < value.length()) && (value.charAt(offset) == expected);
|
return (offset < value.length()) && (value.charAt(offset) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an integer located between 2 given offsets in a string
|
||||||
|
*
|
||||||
|
* @param value the string to parse
|
||||||
|
* @param beginIndex the start index for the integer in the string
|
||||||
|
* @param endIndex the end index for the integer in the string
|
||||||
|
* @return the int
|
||||||
|
* @throws NumberFormatException if the value is not a number
|
||||||
|
*/
|
||||||
|
private static int parseInt(String value, int beginIndex, int endIndex)
|
||||||
|
throws NumberFormatException {
|
||||||
|
if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) {
|
||||||
|
throw new NumberFormatException(value);
|
||||||
}
|
}
|
||||||
|
// use same logic as in Integer.parseInt() but less generic we're not supporting negative values
|
||||||
/**
|
int i = beginIndex;
|
||||||
* Parse an integer located between 2 given offsets in a string
|
int result = 0;
|
||||||
*
|
int digit;
|
||||||
* @param value the string to parse
|
if (i < endIndex) {
|
||||||
* @param beginIndex the start index for the integer in the string
|
digit = Character.digit(value.charAt(i++), 10);
|
||||||
* @param endIndex the end index for the integer in the string
|
if (digit < 0) {
|
||||||
* @return the int
|
throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex));
|
||||||
* @throws NumberFormatException if the value is not a number
|
}
|
||||||
*/
|
result = -digit;
|
||||||
private static int parseInt(String value, int beginIndex, int endIndex) throws NumberFormatException {
|
|
||||||
if (beginIndex < 0 || endIndex > value.length() || beginIndex > endIndex) {
|
|
||||||
throw new NumberFormatException(value);
|
|
||||||
}
|
|
||||||
// use same logic as in Integer.parseInt() but less generic we're not supporting negative values
|
|
||||||
int i = beginIndex;
|
|
||||||
int result = 0;
|
|
||||||
int digit;
|
|
||||||
if (i < endIndex) {
|
|
||||||
digit = Character.digit(value.charAt(i++), 10);
|
|
||||||
if (digit < 0) {
|
|
||||||
throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex));
|
|
||||||
}
|
|
||||||
result = -digit;
|
|
||||||
}
|
|
||||||
while (i < endIndex) {
|
|
||||||
digit = Character.digit(value.charAt(i++), 10);
|
|
||||||
if (digit < 0) {
|
|
||||||
throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex));
|
|
||||||
}
|
|
||||||
result *= 10;
|
|
||||||
result -= digit;
|
|
||||||
}
|
|
||||||
return -result;
|
|
||||||
}
|
}
|
||||||
|
while (i < endIndex) {
|
||||||
/**
|
digit = Character.digit(value.charAt(i++), 10);
|
||||||
* Zero pad a number to a specified length
|
if (digit < 0) {
|
||||||
*
|
throw new NumberFormatException("Invalid number: " + value.substring(beginIndex, endIndex));
|
||||||
* @param buffer buffer to use for padding
|
}
|
||||||
* @param value the integer value to pad if necessary.
|
result *= 10;
|
||||||
* @param length the length of the string we should zero pad
|
result -= digit;
|
||||||
*/
|
|
||||||
private static void padInt(StringBuilder buffer, int value, int length) {
|
|
||||||
String strValue = Integer.toString(value);
|
|
||||||
for (int i = length - strValue.length(); i > 0; i--) {
|
|
||||||
buffer.append('0');
|
|
||||||
}
|
|
||||||
buffer.append(strValue);
|
|
||||||
}
|
}
|
||||||
|
return -result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the index of the first character in the string that is not a digit, starting at offset.
|
* Zero pad a number to a specified length
|
||||||
*/
|
*
|
||||||
private static int indexOfNonDigit(String string, int offset) {
|
* @param buffer buffer to use for padding
|
||||||
for (int i = offset; i < string.length(); i++) {
|
* @param value the integer value to pad if necessary.
|
||||||
char c = string.charAt(i);
|
* @param length the length of the string we should zero pad
|
||||||
if (c < '0' || c > '9') return i;
|
*/
|
||||||
}
|
private static void padInt(StringBuilder buffer, int value, int length) {
|
||||||
return string.length();
|
String strValue = Integer.toString(value);
|
||||||
|
for (int i = length - strValue.length(); i > 0; i--) {
|
||||||
|
buffer.append('0');
|
||||||
}
|
}
|
||||||
|
buffer.append(strValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the first character in the string that is not a digit, starting at offset.
|
||||||
|
*/
|
||||||
|
private static int indexOfNonDigit(String string, int offset) {
|
||||||
|
for (int i = offset; i < string.length(); i++) {
|
||||||
|
char c = string.charAt(i);
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.length();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the Gson serialization/deserialization API.
|
* Defines the Gson serialization/deserialization API.
|
||||||
|
*
|
||||||
* @since 2.8.6
|
* @since 2.8.6
|
||||||
*/
|
*/
|
||||||
module io.gitlab.jfronny.gson {
|
module io.gitlab.jfronny.gson {
|
||||||
|
|
|
@ -24,17 +24,17 @@ import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for the default serializer/deserializer for the {@code InetAddress} type.
|
* Unit tests for the default serializer/deserializer for the {@code InetAddress} type.
|
||||||
*
|
*
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
public class DefaultInetAddressTypeAdapterTest {
|
public class DefaultInetAddressTypeAdapterTest {
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
gson = new Gson();
|
gson = new Gson();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInetAddressSerializationAndDeserialization() throws Exception {
|
public void testInetAddressSerializationAndDeserialization() throws Exception {
|
||||||
@SuppressWarnings("AddressSelection") // we really do want this method
|
@SuppressWarnings("AddressSelection") // we really do want this method
|
||||||
|
|
|
@ -25,8 +25,8 @@ import java.util.Map;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for the default JSON map serialization object located in the
|
* Unit test for the default JSON map serialization object located in the {@link
|
||||||
* {@link DefaultTypeAdapters} class.
|
* DefaultTypeAdapters} class.
|
||||||
*
|
*
|
||||||
* @author Joel Leitch
|
* @author Joel Leitch
|
||||||
*/
|
*/
|
||||||
|
@ -44,7 +44,7 @@ public class DefaultMapJsonSerializerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyMapSerialization() {
|
public void testEmptyMapSerialization() {
|
||||||
Type mapType = new TypeToken<Map<String, String>>() { }.getType();
|
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
|
||||||
Map<String, String> emptyMap = new HashMap<>();
|
Map<String, String> emptyMap = new HashMap<>();
|
||||||
JsonElement element = gson.toJsonTree(emptyMap, mapType);
|
JsonElement element = gson.toJsonTree(emptyMap, mapType);
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public class DefaultMapJsonSerializerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNonEmptyMapSerialization() {
|
public void testNonEmptyMapSerialization() {
|
||||||
Type mapType = new TypeToken<Map<String, String>>() { }.getType();
|
Type mapType = new TypeToken<Map<String, String>>() {}.getType();
|
||||||
Map<String, String> myMap = new HashMap<>();
|
Map<String, String> myMap = new HashMap<>();
|
||||||
String key = "key1";
|
String key = "key1";
|
||||||
myMap.put(key, "value1");
|
myMap.put(key, "value1");
|
||||||
|
|
|
@ -31,38 +31,48 @@ import org.junit.Test;
|
||||||
public class ExposeAnnotationExclusionStrategyTest {
|
public class ExposeAnnotationExclusionStrategyTest {
|
||||||
private Excluder excluder = Excluder.DEFAULT.excludeFieldsWithoutExposeAnnotation();
|
private Excluder excluder = Excluder.DEFAULT.excludeFieldsWithoutExposeAnnotation();
|
||||||
|
|
||||||
|
private void assertIncludesClass(Class<?> c) {
|
||||||
|
assertThat(excluder.excludeClass(c, true)).isFalse();
|
||||||
|
assertThat(excluder.excludeClass(c, false)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIncludesField(Field f) {
|
||||||
|
assertThat(excluder.excludeField(f, true)).isFalse();
|
||||||
|
assertThat(excluder.excludeField(f, false)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertExcludesField(Field f) {
|
||||||
|
assertThat(excluder.excludeField(f, true)).isTrue();
|
||||||
|
assertThat(excluder.excludeField(f, false)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNeverSkipClasses() {
|
public void testNeverSkipClasses() {
|
||||||
assertThat(excluder.excludeClass(MockObject.class, true)).isFalse();
|
assertIncludesClass(MockObject.class);
|
||||||
assertThat(excluder.excludeClass(MockObject.class, false)).isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSkipNonAnnotatedFields() throws Exception {
|
public void testSkipNonAnnotatedFields() throws Exception {
|
||||||
Field f = createFieldAttributes("hiddenField");
|
Field f = createFieldAttributes("hiddenField");
|
||||||
assertThat(excluder.excludeField(f, true)).isTrue();
|
assertExcludesField(f);
|
||||||
assertThat(excluder.excludeField(f, false)).isTrue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSkipExplicitlySkippedFields() throws Exception {
|
public void testSkipExplicitlySkippedFields() throws Exception {
|
||||||
Field f = createFieldAttributes("explicitlyHiddenField");
|
Field f = createFieldAttributes("explicitlyHiddenField");
|
||||||
assertThat(excluder.excludeField(f, true)).isTrue();
|
assertExcludesField(f);
|
||||||
assertThat(excluder.excludeField(f, false)).isTrue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNeverSkipExposedAnnotatedFields() throws Exception {
|
public void testNeverSkipExposedAnnotatedFields() throws Exception {
|
||||||
Field f = createFieldAttributes("exposedField");
|
Field f = createFieldAttributes("exposedField");
|
||||||
assertThat(excluder.excludeField(f, true)).isFalse();
|
assertIncludesField(f);
|
||||||
assertThat(excluder.excludeField(f, false)).isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNeverSkipExplicitlyExposedAnnotatedFields() throws Exception {
|
public void testNeverSkipExplicitlyExposedAnnotatedFields() throws Exception {
|
||||||
Field f = createFieldAttributes("explicitlyExposedField");
|
Field f = createFieldAttributes("explicitlyExposedField");
|
||||||
assertThat(excluder.excludeField(f, true)).isFalse();
|
assertIncludesField(f);
|
||||||
assertThat(excluder.excludeField(f, false)).isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -78,16 +88,15 @@ public class ExposeAnnotationExclusionStrategyTest {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static class MockObject {
|
private static class MockObject {
|
||||||
@Expose
|
@Expose public final int exposedField = 0;
|
||||||
public final int exposedField = 0;
|
|
||||||
|
|
||||||
@Expose(serialize=true, deserialize=true)
|
@Expose(serialize = true, deserialize = true)
|
||||||
public final int explicitlyExposedField = 0;
|
public final int explicitlyExposedField = 0;
|
||||||
|
|
||||||
@Expose(serialize=false, deserialize=false)
|
@Expose(serialize = false, deserialize = false)
|
||||||
public final int explicitlyHiddenField = 0;
|
public final int explicitlyHiddenField = 0;
|
||||||
|
|
||||||
@Expose(serialize=true, deserialize=false)
|
@Expose(serialize = true, deserialize = false)
|
||||||
public final int explicitlyDifferentModeField = 0;
|
public final int explicitlyDifferentModeField = 0;
|
||||||
|
|
||||||
public final int hiddenField = 0;
|
public final int hiddenField = 0;
|
||||||
|
|
|
@ -46,7 +46,8 @@ public class FieldAttributesTest {
|
||||||
try {
|
try {
|
||||||
new FieldAttributes(null);
|
new FieldAttributes(null);
|
||||||
fail("Field parameter can not be null");
|
fail("Field parameter can not be null");
|
||||||
} catch (NullPointerException expected) { }
|
} catch (NullPointerException expected) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -19,29 +19,29 @@ package com.google.gson;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
|
|
||||||
|
import com.google.gson.functional.FieldNamingTest;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import com.google.gson.functional.FieldNamingTest;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs tests directly against {@link FieldNamingPolicy}; for integration tests
|
* Performs tests directly against {@link FieldNamingPolicy}; for integration tests see {@link
|
||||||
* see {@link FieldNamingTest}.
|
* FieldNamingTest}.
|
||||||
*/
|
*/
|
||||||
public class FieldNamingPolicyTest {
|
public class FieldNamingPolicyTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSeparateCamelCase() {
|
public void testSeparateCamelCase() {
|
||||||
// Map from original -> expected
|
// Map from original -> expected
|
||||||
String[][] argumentPairs = {
|
String[][] argumentPairs = {
|
||||||
{ "a", "a" },
|
{"a", "a"},
|
||||||
{ "ab", "ab" },
|
{"ab", "ab"},
|
||||||
{ "Ab", "Ab" },
|
{"Ab", "Ab"},
|
||||||
{ "aB", "a_B" },
|
{"aB", "a_B"},
|
||||||
{ "AB", "A_B" },
|
{"AB", "A_B"},
|
||||||
{ "A_B", "A__B" },
|
{"A_B", "A__B"},
|
||||||
{ "firstSecondThird", "first_Second_Third" },
|
{"firstSecondThird", "first_Second_Third"},
|
||||||
{ "__", "__" },
|
{"__", "__"},
|
||||||
{ "_123", "_123" }
|
{"_123", "_123"}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (String[] pair : argumentPairs) {
|
for (String[] pair : argumentPairs) {
|
||||||
|
@ -52,19 +52,19 @@ public class FieldNamingPolicyTest {
|
||||||
@Test
|
@Test
|
||||||
public void testUpperCaseFirstLetter() {
|
public void testUpperCaseFirstLetter() {
|
||||||
// Map from original -> expected
|
// Map from original -> expected
|
||||||
String[][] argumentPairs = {
|
String[][] argumentPairs = {
|
||||||
{ "a", "A" },
|
{"a", "A"},
|
||||||
{ "ab", "Ab" },
|
{"ab", "Ab"},
|
||||||
{ "AB", "AB" },
|
{"AB", "AB"},
|
||||||
{ "_a", "_A" },
|
{"_a", "_A"},
|
||||||
{ "_ab", "_Ab" },
|
{"_ab", "_Ab"},
|
||||||
{ "__", "__" },
|
{"__", "__"},
|
||||||
{ "_1", "_1" },
|
{"_1", "_1"},
|
||||||
// Not a letter, but has uppercase variant (should not be uppercased)
|
// Not a letter, but has uppercase variant (should not be uppercased)
|
||||||
// See https://github.com/google/gson/issues/1965
|
// See https://github.com/google/gson/issues/1965
|
||||||
{ "\u2170", "\u2170" },
|
{"\u2170", "\u2170"},
|
||||||
{ "_\u2170", "_\u2170" },
|
{"_\u2170", "_\u2170"},
|
||||||
{ "\u2170a", "\u2170A" },
|
{"\u2170a", "\u2170A"},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (String[] pair : argumentPairs) {
|
for (String[] pair : argumentPairs) {
|
||||||
|
@ -72,9 +72,7 @@ public class FieldNamingPolicyTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Upper-casing policies should be unaffected by default Locale. */
|
||||||
* Upper-casing policies should be unaffected by default Locale.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpperCasingLocaleIndependent() throws Exception {
|
public void testUpperCasingLocaleIndependent() throws Exception {
|
||||||
class Dummy {
|
class Dummy {
|
||||||
|
@ -99,25 +97,25 @@ public class FieldNamingPolicyTest {
|
||||||
try {
|
try {
|
||||||
// Verify that default Locale has different case conversion rules
|
// Verify that default Locale has different case conversion rules
|
||||||
assertWithMessage("Test setup is broken")
|
assertWithMessage("Test setup is broken")
|
||||||
.that(name.toUpperCase(Locale.getDefault())).doesNotMatch(expected);
|
.that(name.toUpperCase(Locale.getDefault()))
|
||||||
|
.doesNotMatch(expected);
|
||||||
|
|
||||||
for (FieldNamingPolicy policy : policies) {
|
for (FieldNamingPolicy policy : policies) {
|
||||||
// Should ignore default Locale
|
// Should ignore default Locale
|
||||||
assertWithMessage("Unexpected conversion for %s", policy)
|
assertWithMessage("Unexpected conversion for %s", policy)
|
||||||
.that(policy.translateName(field)).matches(expected);
|
.that(policy.translateName(field))
|
||||||
|
.matches(expected);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Locale.setDefault(oldLocale);
|
Locale.setDefault(oldLocale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Lower casing policies should be unaffected by default Locale. */
|
||||||
* Lower casing policies should be unaffected by default Locale.
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testLowerCasingLocaleIndependent() throws Exception {
|
public void testLowerCasingLocaleIndependent() throws Exception {
|
||||||
class Dummy {
|
class Dummy {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings({"unused", "ConstantField"})
|
||||||
int I;
|
int I;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,15 +136,17 @@ public class FieldNamingPolicyTest {
|
||||||
try {
|
try {
|
||||||
// Verify that default Locale has different case conversion rules
|
// Verify that default Locale has different case conversion rules
|
||||||
assertWithMessage("Test setup is broken")
|
assertWithMessage("Test setup is broken")
|
||||||
.that(name.toLowerCase(Locale.getDefault())).doesNotMatch(expected);
|
.that(name.toLowerCase(Locale.getDefault()))
|
||||||
|
.doesNotMatch(expected);
|
||||||
|
|
||||||
for (FieldNamingPolicy policy : policies) {
|
for (FieldNamingPolicy policy : policies) {
|
||||||
// Should ignore default Locale
|
// Should ignore default Locale
|
||||||
assertWithMessage("Unexpected conversion for %s", policy)
|
assertWithMessage("Unexpected conversion for %s", policy)
|
||||||
.that(policy.translateName(field)).matches(expected);
|
.that(policy.translateName(field))
|
||||||
|
.matches(expected);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Locale.setDefault(oldLocale);
|
Locale.setDefault(oldLocale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,9 @@ public class GenericArrayTypeTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
ourType = $Gson$Types.arrayOf($Gson$Types.newParameterizedTypeWithOwner(null, List.class, String.class));
|
ourType =
|
||||||
|
$Gson$Types.arrayOf(
|
||||||
|
$Gson$Types.newParameterizedTypeWithOwner(null, List.class, String.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,257 +1,396 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2008 Google Inc.
|
* Copyright (C) 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.gson;
|
package com.google.gson;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonReader;
|
||||||
import java.io.IOException;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import java.io.StringReader;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringReader;
|
||||||
import java.lang.reflect.Field;
|
import java.io.StringWriter;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Modifier;
|
||||||
import org.junit.Test;
|
import java.lang.reflect.Type;
|
||||||
|
import java.text.DateFormat;
|
||||||
/**
|
import java.util.Date;
|
||||||
* Unit tests for {@link GsonBuilder}.
|
import org.junit.Test;
|
||||||
*
|
|
||||||
* @author Inderjeet Singh
|
/**
|
||||||
*/
|
* Unit tests for {@link GsonBuilder}.
|
||||||
public class GsonBuilderTest {
|
*
|
||||||
private static final TypeAdapter<Object> NULL_TYPE_ADAPTER = new TypeAdapter<Object>() {
|
* @author Inderjeet Singh
|
||||||
@Override public void write(JsonWriter out, Object value) {
|
*/
|
||||||
throw new AssertionError();
|
public class GsonBuilderTest {
|
||||||
}
|
private static final TypeAdapter<Object> NULL_TYPE_ADAPTER =
|
||||||
@Override public Object read(JsonReader in) {
|
new TypeAdapter<Object>() {
|
||||||
throw new AssertionError();
|
@Override
|
||||||
}
|
public void write(JsonWriter out, Object value) {
|
||||||
};
|
throw new AssertionError();
|
||||||
|
}
|
||||||
@Test
|
|
||||||
public void testCreatingMoreThanOnce() {
|
@Override
|
||||||
GsonBuilder builder = new GsonBuilder();
|
public Object read(JsonReader in) {
|
||||||
Gson gson = builder.create();
|
throw new AssertionError();
|
||||||
assertThat(gson).isNotNull();
|
}
|
||||||
assertThat(builder.create()).isNotNull();
|
};
|
||||||
|
|
||||||
builder.setFieldNamingStrategy(new FieldNamingStrategy() {
|
@Test
|
||||||
@Override public String translateName(Field f) {
|
public void testCreatingMoreThanOnce() {
|
||||||
return "test";
|
GsonBuilder builder = new GsonBuilder();
|
||||||
}
|
Gson gson = builder.create();
|
||||||
});
|
assertThat(gson).isNotNull();
|
||||||
|
assertThat(builder.create()).isNotNull();
|
||||||
Gson otherGson = builder.create();
|
|
||||||
assertThat(otherGson).isNotNull();
|
builder.setFieldNamingStrategy(
|
||||||
// Should be different instances because builder has been modified in the meantime
|
new FieldNamingStrategy() {
|
||||||
assertThat(gson).isNotSameInstanceAs(otherGson);
|
@Override
|
||||||
}
|
public String translateName(Field f) {
|
||||||
|
return "test";
|
||||||
/**
|
}
|
||||||
* Gson instances should not be affected by subsequent modification of GsonBuilder
|
});
|
||||||
* which created them.
|
|
||||||
*/
|
Gson otherGson = builder.create();
|
||||||
@Test
|
assertThat(otherGson).isNotNull();
|
||||||
public void testModificationAfterCreate() {
|
// Should be different instances because builder has been modified in the meantime
|
||||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
assertThat(gson).isNotSameInstanceAs(otherGson);
|
||||||
Gson gson = gsonBuilder.create();
|
}
|
||||||
|
|
||||||
// Modifications of `gsonBuilder` should not affect `gson` object
|
/**
|
||||||
gsonBuilder.registerTypeAdapter(CustomClass1.class, new TypeAdapter<CustomClass1>() {
|
* Gson instances should not be affected by subsequent modification of GsonBuilder which created
|
||||||
@Override public CustomClass1 read(JsonReader in) {
|
* them.
|
||||||
throw new UnsupportedOperationException();
|
*/
|
||||||
}
|
@Test
|
||||||
|
public void testModificationAfterCreate() {
|
||||||
@Override public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
out.value("custom-adapter");
|
Gson gson = gsonBuilder.create();
|
||||||
}
|
|
||||||
});
|
// Modifications of `gsonBuilder` should not affect `gson` object
|
||||||
gsonBuilder.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
|
gsonBuilder.registerTypeAdapter(
|
||||||
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
CustomClass1.class,
|
||||||
return new JsonPrimitive("custom-hierarchy-adapter");
|
new TypeAdapter<CustomClass1>() {
|
||||||
}
|
@Override
|
||||||
});
|
public CustomClass1 read(JsonReader in) {
|
||||||
gsonBuilder.registerTypeAdapter(CustomClass3.class, new InstanceCreator<CustomClass3>() {
|
throw new UnsupportedOperationException();
|
||||||
@Override public CustomClass3 createInstance(Type type) {
|
}
|
||||||
return new CustomClass3("custom-instance");
|
|
||||||
}
|
@Override
|
||||||
});
|
public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
||||||
|
out.value("custom-adapter");
|
||||||
|
}
|
||||||
assertDefaultGson(gson);
|
});
|
||||||
// New GsonBuilder created from `gson` should not have been affected by changes
|
gsonBuilder.registerTypeHierarchyAdapter(
|
||||||
// to `gsonBuilder` either
|
CustomClass2.class,
|
||||||
assertDefaultGson(gson.newBuilder().create());
|
new JsonSerializer<CustomClass2>() {
|
||||||
|
@Override
|
||||||
// New Gson instance from modified GsonBuilder should be affected by changes
|
public JsonElement serialize(
|
||||||
assertCustomGson(gsonBuilder.create());
|
CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
}
|
return new JsonPrimitive("custom-hierarchy-adapter");
|
||||||
|
}
|
||||||
private static void assertDefaultGson(Gson gson) {
|
});
|
||||||
// Should use default reflective adapter
|
gsonBuilder.registerTypeAdapter(
|
||||||
String json1 = gson.toJson(new CustomClass1());
|
CustomClass3.class,
|
||||||
assertThat(json1).isEqualTo("{}");
|
new InstanceCreator<CustomClass3>() {
|
||||||
|
@Override
|
||||||
// Should use default reflective adapter
|
public CustomClass3 createInstance(Type type) {
|
||||||
String json2 = gson.toJson(new CustomClass2());
|
return new CustomClass3("custom-instance");
|
||||||
assertThat(json2).isEqualTo("{}");
|
}
|
||||||
|
});
|
||||||
// Should use default instance creator
|
|
||||||
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
|
assertDefaultGson(gson);
|
||||||
assertThat(customClass3.s).isEqualTo(CustomClass3.NO_ARG_CONSTRUCTOR_VALUE);
|
// New GsonBuilder created from `gson` should not have been affected by changes
|
||||||
}
|
// to `gsonBuilder` either
|
||||||
|
assertDefaultGson(gson.newBuilder().create());
|
||||||
private static void assertCustomGson(Gson gson) {
|
|
||||||
String json1 = gson.toJson(new CustomClass1());
|
// New Gson instance from modified GsonBuilder should be affected by changes
|
||||||
assertThat(json1).isEqualTo("\"custom-adapter\"");
|
assertCustomGson(gsonBuilder.create());
|
||||||
|
}
|
||||||
String json2 = gson.toJson(new CustomClass2());
|
|
||||||
assertThat(json2).isEqualTo("\"custom-hierarchy-adapter\"");
|
private static void assertDefaultGson(Gson gson) {
|
||||||
|
// Should use default reflective adapter
|
||||||
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
|
String json1 = gson.toJson(new CustomClass1());
|
||||||
assertThat(customClass3.s).isEqualTo("custom-instance");
|
assertThat(json1).isEqualTo("{}");
|
||||||
}
|
|
||||||
|
// Should use default reflective adapter
|
||||||
static class CustomClass1 { }
|
String json2 = gson.toJson(new CustomClass2());
|
||||||
static class CustomClass2 { }
|
assertThat(json2).isEqualTo("{}");
|
||||||
static class CustomClass3 {
|
|
||||||
static final String NO_ARG_CONSTRUCTOR_VALUE = "default instance";
|
// Should use default instance creator
|
||||||
|
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
|
||||||
final String s;
|
assertThat(customClass3.s).isEqualTo(CustomClass3.NO_ARG_CONSTRUCTOR_VALUE);
|
||||||
|
}
|
||||||
public CustomClass3(String s) {
|
|
||||||
this.s = s;
|
private static void assertCustomGson(Gson gson) {
|
||||||
}
|
String json1 = gson.toJson(new CustomClass1());
|
||||||
|
assertThat(json1).isEqualTo("\"custom-adapter\"");
|
||||||
public CustomClass3() {
|
|
||||||
this(NO_ARG_CONSTRUCTOR_VALUE);
|
String json2 = gson.toJson(new CustomClass2());
|
||||||
}
|
assertThat(json2).isEqualTo("\"custom-hierarchy-adapter\"");
|
||||||
}
|
|
||||||
|
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
|
||||||
@Test
|
assertThat(customClass3.s).isEqualTo("custom-instance");
|
||||||
public void testExcludeFieldsWithModifiers() {
|
}
|
||||||
Gson gson = new GsonBuilder()
|
|
||||||
.excludeFieldsWithModifiers(Modifier.VOLATILE, Modifier.PRIVATE)
|
static class CustomClass1 {}
|
||||||
.create();
|
|
||||||
assertThat(gson.toJson(new HasModifiers())).isEqualTo("{\"d\":\"d\"}");
|
static class CustomClass2 {}
|
||||||
}
|
|
||||||
|
static class CustomClass3 {
|
||||||
@SuppressWarnings("unused")
|
static final String NO_ARG_CONSTRUCTOR_VALUE = "default instance";
|
||||||
static class HasModifiers {
|
|
||||||
private String a = "a";
|
final String s;
|
||||||
volatile String b = "b";
|
|
||||||
private volatile String c = "c";
|
public CustomClass3(String s) {
|
||||||
String d = "d";
|
this.s = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
public CustomClass3() {
|
||||||
public void testTransientFieldExclusion() {
|
this(NO_ARG_CONSTRUCTOR_VALUE);
|
||||||
Gson gson = new GsonBuilder()
|
}
|
||||||
.excludeFieldsWithModifiers()
|
}
|
||||||
.create();
|
|
||||||
assertThat(gson.toJson(new HasTransients())).isEqualTo("{\"a\":\"a\"}");
|
@Test
|
||||||
}
|
public void testExcludeFieldsWithModifiers() {
|
||||||
|
Gson gson =
|
||||||
static class HasTransients {
|
new GsonBuilder().excludeFieldsWithModifiers(Modifier.VOLATILE, Modifier.PRIVATE).create();
|
||||||
transient String a = "a";
|
assertThat(gson.toJson(new HasModifiers())).isEqualTo("{\"d\":\"d\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@SuppressWarnings("unused")
|
||||||
public void testRegisterTypeAdapterForCoreType() {
|
static class HasModifiers {
|
||||||
Type[] types = {
|
private String a = "a";
|
||||||
byte.class,
|
volatile String b = "b";
|
||||||
int.class,
|
private volatile String c = "c";
|
||||||
double.class,
|
String d = "d";
|
||||||
Short.class,
|
}
|
||||||
Long.class,
|
|
||||||
String.class,
|
@Test
|
||||||
};
|
public void testTransientFieldExclusion() {
|
||||||
for (Type type : types) {
|
Gson gson = new GsonBuilder().excludeFieldsWithModifiers().create();
|
||||||
new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
|
assertThat(gson.toJson(new HasTransients())).isEqualTo("{\"a\":\"a\"}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
static class HasTransients {
|
||||||
@Test
|
transient String a = "a";
|
||||||
public void testDisableJdkUnsafe() {
|
}
|
||||||
Gson gson = new GsonBuilder()
|
|
||||||
.disableJdkUnsafe()
|
@Test
|
||||||
.create();
|
public void testRegisterTypeAdapterForCoreType() {
|
||||||
try {
|
Type[] types = {
|
||||||
gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
|
byte.class, int.class, double.class, Short.class, Long.class, String.class,
|
||||||
fail("Expected exception");
|
};
|
||||||
} catch (JsonIOException expected) {
|
for (Type type : types) {
|
||||||
assertThat(expected).hasMessageThat().isEqualTo(
|
new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
|
||||||
"Unable to create instance of class com.google.gson.GsonBuilderTest$ClassWithoutNoArgsConstructor; "
|
}
|
||||||
+ "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.");
|
|
||||||
}
|
@Test
|
||||||
}
|
public void testDisableJdkUnsafe() {
|
||||||
|
Gson gson = new GsonBuilder().disableJdkUnsafe().create();
|
||||||
private static class ClassWithoutNoArgsConstructor {
|
try {
|
||||||
@SuppressWarnings("unused")
|
gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
|
||||||
public ClassWithoutNoArgsConstructor(String s) {
|
fail("Expected exception");
|
||||||
}
|
} catch (JsonIOException expected) {
|
||||||
}
|
assertThat(expected)
|
||||||
|
.hasMessageThat()
|
||||||
@Test
|
.isEqualTo(
|
||||||
public void testSetVersionInvalid() {
|
"Unable to create instance of class"
|
||||||
GsonBuilder builder = new GsonBuilder();
|
+ " com.google.gson.GsonBuilderTest$ClassWithoutNoArgsConstructor; usage of JDK"
|
||||||
try {
|
+ " Unsafe is disabled. Registering an InstanceCreator or a TypeAdapter for this"
|
||||||
builder.setVersion(Double.NaN);
|
+ " type, adding a no-args constructor, or enabling usage of JDK Unsafe may fix"
|
||||||
fail();
|
+ " this problem.");
|
||||||
} catch (IllegalArgumentException e) {
|
}
|
||||||
assertThat(e).hasMessageThat().isEqualTo("Invalid version: NaN");
|
}
|
||||||
}
|
|
||||||
|
private static class ClassWithoutNoArgsConstructor {
|
||||||
try {
|
@SuppressWarnings("unused")
|
||||||
builder.setVersion(-0.1);
|
public ClassWithoutNoArgsConstructor(String s) {}
|
||||||
fail();
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
assertThat(e).hasMessageThat().isEqualTo("Invalid version: -0.1");
|
@Test
|
||||||
}
|
public void testSetVersionInvalid() {
|
||||||
}
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
try {
|
||||||
@Test
|
builder.setVersion(Double.NaN);
|
||||||
public void testDefaultStrictness() throws IOException {
|
fail();
|
||||||
GsonBuilder builder = new GsonBuilder();
|
} catch (IllegalArgumentException e) {
|
||||||
Gson gson = builder.create();
|
assertThat(e).hasMessageThat().isEqualTo("Invalid version: NaN");
|
||||||
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
|
}
|
||||||
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(Strictness.LEGACY_STRICT);
|
|
||||||
}
|
try {
|
||||||
|
builder.setVersion(-0.1);
|
||||||
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
|
fail();
|
||||||
@Test
|
} catch (IllegalArgumentException e) {
|
||||||
public void testSetLenient() throws IOException {
|
assertThat(e).hasMessageThat().isEqualTo("Invalid version: -0.1");
|
||||||
GsonBuilder builder = new GsonBuilder();
|
}
|
||||||
builder.setLenient();
|
}
|
||||||
Gson gson = builder.create();
|
|
||||||
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness()).isEqualTo(Strictness.LENIENT);
|
@Test
|
||||||
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(Strictness.LENIENT);
|
public void testDefaultStrictness() throws IOException {
|
||||||
}
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
Gson gson = builder.create();
|
||||||
@Test
|
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness())
|
||||||
public void testSetStrictness() throws IOException {
|
.isEqualTo(Strictness.LEGACY_STRICT);
|
||||||
final Strictness STRICTNESS = Strictness.STRICT;
|
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness())
|
||||||
GsonBuilder builder = new GsonBuilder();
|
.isEqualTo(Strictness.LEGACY_STRICT);
|
||||||
builder.setStrictness(STRICTNESS);
|
}
|
||||||
Gson gson = builder.create();
|
|
||||||
assertThat(gson.newJsonReader(new StringReader("{}")).getStrictness()).isEqualTo(STRICTNESS);
|
@SuppressWarnings({"deprecation", "InlineMeInliner"}) // for GsonBuilder.setLenient
|
||||||
assertThat(gson.newJsonWriter(new StringWriter()).getStrictness()).isEqualTo(STRICTNESS);
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegisterTypeAdapterForObjectAndJsonElements() {
|
||||||
|
final String ERROR_MESSAGE = "Cannot override built-in adapter for ";
|
||||||
|
Type[] types = {
|
||||||
|
Object.class, JsonElement.class, JsonArray.class,
|
||||||
|
};
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
for (Type type : types) {
|
||||||
|
IllegalArgumentException e =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> gsonBuilder.registerTypeAdapter(type, NULL_TYPE_ADAPTER));
|
||||||
|
assertThat(e).hasMessageThat().isEqualTo(ERROR_MESSAGE + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegisterTypeHierarchyAdapterJsonElements() {
|
||||||
|
final String ERROR_MESSAGE = "Cannot override built-in adapter for ";
|
||||||
|
Class<?>[] types = {
|
||||||
|
JsonElement.class, JsonArray.class,
|
||||||
|
};
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
for (Class<?> type : types) {
|
||||||
|
IllegalArgumentException e =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> gsonBuilder.registerTypeHierarchyAdapter(type, NULL_TYPE_ADAPTER));
|
||||||
|
|
||||||
|
assertThat(e).hasMessageThat().isEqualTo(ERROR_MESSAGE + type);
|
||||||
|
}
|
||||||
|
// But registering type hierarchy adapter for Object should be allowed
|
||||||
|
gsonBuilder.registerTypeHierarchyAdapter(Object.class, NULL_TYPE_ADAPTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetDateFormatWithInvalidPattern() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
String invalidPattern = "This is an invalid Pattern";
|
||||||
|
IllegalArgumentException e =
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> builder.setDateFormat(invalidPattern));
|
||||||
|
assertThat(e)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo("The date pattern '" + invalidPattern + "' is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetDateFormatWithValidPattern() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
String validPattern = "yyyy-MM-dd";
|
||||||
|
// Should not throw an exception
|
||||||
|
builder.setDateFormat(validPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetDateFormatNullPattern() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
@SuppressWarnings("JavaUtilDate")
|
||||||
|
Date date = new Date(0);
|
||||||
|
String originalFormatted = builder.create().toJson(date);
|
||||||
|
|
||||||
|
String customFormatted = builder.setDateFormat("yyyy-MM-dd").create().toJson(date);
|
||||||
|
assertThat(customFormatted).isNotEqualTo(originalFormatted);
|
||||||
|
|
||||||
|
// `null` should reset the format to the default
|
||||||
|
String resetFormatted = builder.setDateFormat(null).create().toJson(date);
|
||||||
|
assertThat(resetFormatted).isEqualTo(originalFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests behavior for an empty date pattern; this behavior is not publicly documented at the
|
||||||
|
* moment.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSetDateFormatEmptyPattern() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
@SuppressWarnings("JavaUtilDate")
|
||||||
|
Date date = new Date(0);
|
||||||
|
String originalFormatted = builder.create().toJson(date);
|
||||||
|
|
||||||
|
String emptyFormatted = builder.setDateFormat(" ").create().toJson(date);
|
||||||
|
// Empty pattern was ignored
|
||||||
|
assertThat(emptyFormatted).isEqualTo(originalFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // for GsonBuilder.setDateFormat(int)
|
||||||
|
@Test
|
||||||
|
public void testSetDateFormatValidStyle() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
int[] validStyles = {DateFormat.FULL, DateFormat.LONG, DateFormat.MEDIUM, DateFormat.SHORT};
|
||||||
|
|
||||||
|
for (int style : validStyles) {
|
||||||
|
// Should not throw an exception
|
||||||
|
builder.setDateFormat(style);
|
||||||
|
builder.setDateFormat(style, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // for GsonBuilder.setDateFormat(int)
|
||||||
|
@Test
|
||||||
|
public void testSetDateFormatInvalidStyle() {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
|
||||||
|
IllegalArgumentException e =
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> builder.setDateFormat(-1));
|
||||||
|
assertThat(e).hasMessageThat().isEqualTo("Invalid style: -1");
|
||||||
|
|
||||||
|
e = assertThrows(IllegalArgumentException.class, () -> builder.setDateFormat(4));
|
||||||
|
assertThat(e).hasMessageThat().isEqualTo("Invalid style: 4");
|
||||||
|
|
||||||
|
e =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class, () -> builder.setDateFormat(-1, DateFormat.FULL));
|
||||||
|
assertThat(e).hasMessageThat().isEqualTo("Invalid style: -1");
|
||||||
|
|
||||||
|
e =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class, () -> builder.setDateFormat(DateFormat.FULL, -1));
|
||||||
|
assertThat(e).hasMessageThat().isEqualTo("Invalid style: -1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -46,18 +46,20 @@ import org.junit.Test;
|
||||||
*/
|
*/
|
||||||
public final class GsonTest {
|
public final class GsonTest {
|
||||||
|
|
||||||
private static final Excluder CUSTOM_EXCLUDER = Excluder.DEFAULT
|
private static final Excluder CUSTOM_EXCLUDER =
|
||||||
.excludeFieldsWithoutExposeAnnotation()
|
Excluder.DEFAULT.excludeFieldsWithoutExposeAnnotation().disableInnerClassSerialization();
|
||||||
.disableInnerClassSerialization();
|
|
||||||
|
|
||||||
private static final FieldNamingStrategy CUSTOM_FIELD_NAMING_STRATEGY = new FieldNamingStrategy() {
|
private static final FieldNamingStrategy CUSTOM_FIELD_NAMING_STRATEGY =
|
||||||
@Override public String translateName(Field f) {
|
new FieldNamingStrategy() {
|
||||||
return "foo";
|
@Override
|
||||||
}
|
public String translateName(Field f) {
|
||||||
};
|
return "foo";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE;
|
private static final ToNumberStrategy CUSTOM_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE;
|
||||||
private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER;
|
private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY =
|
||||||
|
ToNumberPolicy.LAZILY_PARSED_NUMBER;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStrictnessDefault() {
|
public void testStrictnessDefault() {
|
||||||
|
@ -66,14 +68,31 @@ public final class GsonTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOverridesDefaultExcluder() {
|
public void testOverridesDefaultExcluder() {
|
||||||
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
|
Gson gson =
|
||||||
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false, false,
|
new Gson(
|
||||||
FormattingStyle.PRETTY, Strictness.LENIENT, false, false, true,
|
CUSTOM_EXCLUDER,
|
||||||
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
CUSTOM_FIELD_NAMING_STRATEGY,
|
||||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
new HashMap<Type, InstanceCreator<?>>(),
|
||||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
true,
|
||||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
|
false,
|
||||||
Collections.<ReflectionAccessFilter>emptyList());
|
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>(),
|
||||||
|
CUSTOM_OBJECT_TO_NUMBER_STRATEGY,
|
||||||
|
CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
|
||||||
|
Collections.<ReflectionAccessFilter>emptyList());
|
||||||
|
|
||||||
assertThat(gson.excluder).isEqualTo(CUSTOM_EXCLUDER);
|
assertThat(gson.excluder).isEqualTo(CUSTOM_EXCLUDER);
|
||||||
assertThat(gson.fieldNamingStrategy()).isEqualTo(CUSTOM_FIELD_NAMING_STRATEGY);
|
assertThat(gson.fieldNamingStrategy()).isEqualTo(CUSTOM_FIELD_NAMING_STRATEGY);
|
||||||
|
@ -83,44 +102,68 @@ public final class GsonTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClonedTypeAdapterFactoryListsAreIndependent() {
|
public void testClonedTypeAdapterFactoryListsAreIndependent() {
|
||||||
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
|
Gson original =
|
||||||
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false, false,
|
new Gson(
|
||||||
FormattingStyle.PRETTY, Strictness.LENIENT, false, false, true,
|
CUSTOM_EXCLUDER,
|
||||||
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
CUSTOM_FIELD_NAMING_STRATEGY,
|
||||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
new HashMap<Type, InstanceCreator<?>>(),
|
||||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
true,
|
||||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
|
false,
|
||||||
Collections.<ReflectionAccessFilter>emptyList());
|
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>(),
|
||||||
|
CUSTOM_OBJECT_TO_NUMBER_STRATEGY,
|
||||||
|
CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
|
||||||
|
Collections.<ReflectionAccessFilter>emptyList());
|
||||||
|
|
||||||
Gson clone = original.newBuilder()
|
Gson clone =
|
||||||
.registerTypeAdapter(Object.class, new TestTypeAdapter())
|
original.newBuilder().registerTypeAdapter(int.class, new TestTypeAdapter()).create();
|
||||||
.create();
|
|
||||||
|
|
||||||
assertThat(clone.factories).hasSize(original.factories.size() + 1);
|
assertThat(clone.factories).hasSize(original.factories.size() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TestTypeAdapter extends TypeAdapter<Object> {
|
private static final class TestTypeAdapter extends TypeAdapter<Object> {
|
||||||
@Override public void write(JsonWriter out, Object value) {
|
@Override
|
||||||
|
public void write(JsonWriter out, Object value) {
|
||||||
// Test stub.
|
// Test stub.
|
||||||
}
|
}
|
||||||
@Override public Object read(JsonReader in) { return null; }
|
|
||||||
|
@Override
|
||||||
|
public Object read(JsonReader in) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAdapter_Null() {
|
public void testGetAdapter_Null() {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
NullPointerException e = assertThrows(NullPointerException.class, () -> gson.getAdapter((TypeToken<?>) null));
|
NullPointerException e =
|
||||||
|
assertThrows(NullPointerException.class, () -> gson.getAdapter((TypeToken<?>) null));
|
||||||
assertThat(e).hasMessageThat().isEqualTo("type must not be null");
|
assertThat(e).hasMessageThat().isEqualTo("type must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAdapter_Concurrency() {
|
public void testGetAdapter_Concurrency() {
|
||||||
class DummyAdapter<T> extends TypeAdapter<T> {
|
class DummyAdapter<T> extends TypeAdapter<T> {
|
||||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
throw new AssertionError("not needed for this test");
|
throw new AssertionError("not needed for this test");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public T read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
throw new AssertionError("not needed for this test");
|
throw new AssertionError("not needed for this test");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,36 +172,41 @@ public final class GsonTest {
|
||||||
final AtomicReference<TypeAdapter<?>> threadAdapter = new AtomicReference<>();
|
final AtomicReference<TypeAdapter<?>> threadAdapter = new AtomicReference<>();
|
||||||
final Class<?> requestedType = Number.class;
|
final Class<?> requestedType = Number.class;
|
||||||
|
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson =
|
||||||
.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
new GsonBuilder()
|
||||||
private volatile boolean isFirstCall = true;
|
.registerTypeAdapterFactory(
|
||||||
|
new TypeAdapterFactory() {
|
||||||
|
private volatile boolean isFirstCall = true;
|
||||||
|
|
||||||
@Override public <T> TypeAdapter<T> create(final Gson gson, TypeToken<T> type) {
|
@Override
|
||||||
if (isFirstCall) {
|
public <T> TypeAdapter<T> create(final Gson gson, TypeToken<T> type) {
|
||||||
isFirstCall = false;
|
if (isFirstCall) {
|
||||||
|
isFirstCall = false;
|
||||||
|
|
||||||
// Create a separate thread which requests an adapter for the same type
|
// Create a separate thread which requests an adapter for the same type
|
||||||
// This will cause this factory to return a different adapter instance than
|
// This will cause this factory to return a different adapter instance than
|
||||||
// the one it is currently creating
|
// the one it is currently creating
|
||||||
Thread thread = new Thread() {
|
Thread thread =
|
||||||
@Override public void run() {
|
new Thread() {
|
||||||
threadAdapter.set(gson.getAdapter(requestedType));
|
@Override
|
||||||
}
|
public void run() {
|
||||||
};
|
threadAdapter.set(gson.getAdapter(requestedType));
|
||||||
thread.start();
|
}
|
||||||
try {
|
};
|
||||||
thread.join();
|
thread.start();
|
||||||
} catch (InterruptedException e) {
|
try {
|
||||||
throw new RuntimeException(e);
|
thread.join();
|
||||||
}
|
} catch (InterruptedException e) {
|
||||||
}
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new dummy adapter instance
|
// Create a new dummy adapter instance
|
||||||
adapterInstancesCreated.incrementAndGet();
|
adapterInstancesCreated.incrementAndGet();
|
||||||
return new DummyAdapter<>();
|
return new DummyAdapter<>();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
TypeAdapter<?> adapter = gson.getAdapter(requestedType);
|
TypeAdapter<?> adapter = gson.getAdapter(requestedType);
|
||||||
assertThat(adapterInstancesCreated.get()).isEqualTo(2);
|
assertThat(adapterInstancesCreated.get()).isEqualTo(2);
|
||||||
|
@ -167,18 +215,18 @@ public final class GsonTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that two threads calling {@link Gson#getAdapter(TypeToken)} do not see the
|
* Verifies that two threads calling {@link Gson#getAdapter(TypeToken)} do not see the same
|
||||||
* same unresolved {@link FutureTypeAdapter} instance, since that would not be thread-safe.
|
* unresolved {@link FutureTypeAdapter} instance, since that would not be thread-safe.
|
||||||
*
|
*
|
||||||
* This test constructs the cyclic dependency {@literal CustomClass1 -> CustomClass2 -> CustomClass1}
|
* <p>This test constructs the cyclic dependency {@literal CustomClass1 -> CustomClass2 ->
|
||||||
* and lets one thread wait after the adapter for CustomClass2 has been obtained (which still
|
* CustomClass1} and lets one thread wait after the adapter for CustomClass2 has been obtained
|
||||||
* refers to the nested unresolved FutureTypeAdapter for CustomClass1).
|
* (which still refers to the nested unresolved FutureTypeAdapter for CustomClass1).
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testGetAdapter_FutureAdapterConcurrency() throws Exception {
|
public void testGetAdapter_FutureAdapterConcurrency() throws Exception {
|
||||||
/**
|
/**
|
||||||
* Adapter which wraps another adapter. Can be imagined as a simplified version of the
|
* Adapter which wraps another adapter. Can be imagined as a simplified version of the {@code
|
||||||
* {@code ReflectiveTypeAdapterFactory$Adapter}.
|
* ReflectiveTypeAdapterFactory$Adapter}.
|
||||||
*/
|
*/
|
||||||
class WrappingAdapter<T> extends TypeAdapter<T> {
|
class WrappingAdapter<T> extends TypeAdapter<T> {
|
||||||
final TypeAdapter<?> wrapped;
|
final TypeAdapter<?> wrapped;
|
||||||
|
@ -188,7 +236,8 @@ public final class GsonTest {
|
||||||
this.wrapped = wrapped;
|
this.wrapped = wrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, T value) throws IOException {
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
// Due to how this test is set up there is infinite recursion, therefore
|
// Due to how this test is set up there is infinite recursion, therefore
|
||||||
// need to track how deeply nested this call is
|
// need to track how deeply nested this call is
|
||||||
if (isFirstCall) {
|
if (isFirstCall) {
|
||||||
|
@ -202,7 +251,8 @@ public final class GsonTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public T read(JsonReader in) throws IOException {
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
throw new AssertionError("not needed for this test");
|
throw new AssertionError("not needed for this test");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,54 +260,56 @@ public final class GsonTest {
|
||||||
final CountDownLatch isThreadWaiting = new CountDownLatch(1);
|
final CountDownLatch isThreadWaiting = new CountDownLatch(1);
|
||||||
final CountDownLatch canThreadProceed = new CountDownLatch(1);
|
final CountDownLatch canThreadProceed = new CountDownLatch(1);
|
||||||
|
|
||||||
final Gson gson = new GsonBuilder()
|
final Gson gson =
|
||||||
.registerTypeAdapterFactory(new TypeAdapterFactory() {
|
new GsonBuilder()
|
||||||
// volatile instead of AtomicBoolean is safe here because CountDownLatch prevents
|
.registerTypeAdapterFactory(
|
||||||
// "true" concurrency
|
new TypeAdapterFactory() {
|
||||||
volatile boolean isFirstCaller = true;
|
// volatile instead of AtomicBoolean is safe here because CountDownLatch prevents
|
||||||
|
// "true" concurrency
|
||||||
|
volatile boolean isFirstCaller = true;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
Class<?> raw = type.getRawType();
|
Class<?> raw = type.getRawType();
|
||||||
|
|
||||||
if (raw == CustomClass1.class) {
|
if (raw == CustomClass1.class) {
|
||||||
// Retrieves a WrappingAdapter containing a nested FutureAdapter for CustomClass1
|
// Retrieves a WrappingAdapter containing a nested FutureAdapter for
|
||||||
TypeAdapter<?> adapter = gson.getAdapter(CustomClass2.class);
|
// CustomClass1
|
||||||
|
TypeAdapter<?> adapter = gson.getAdapter(CustomClass2.class);
|
||||||
|
|
||||||
// Let thread wait so the FutureAdapter for CustomClass1 nested in the adapter
|
// Let thread wait so the FutureAdapter for CustomClass1 nested in the adapter
|
||||||
// for CustomClass2 is not resolved yet
|
// for CustomClass2 is not resolved yet
|
||||||
if (isFirstCaller) {
|
if (isFirstCaller) {
|
||||||
isFirstCaller = false;
|
isFirstCaller = false;
|
||||||
isThreadWaiting.countDown();
|
isThreadWaiting.countDown();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
canThreadProceed.await();
|
canThreadProceed.await();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WrappingAdapter<>(adapter);
|
return new WrappingAdapter<>(adapter);
|
||||||
}
|
} else if (raw == CustomClass2.class) {
|
||||||
else if (raw == CustomClass2.class) {
|
TypeAdapter<?> adapter = gson.getAdapter(CustomClass1.class);
|
||||||
TypeAdapter<?> adapter = gson.getAdapter(CustomClass1.class);
|
assertThat(adapter).isInstanceOf(FutureTypeAdapter.class);
|
||||||
assertThat(adapter).isInstanceOf(FutureTypeAdapter.class);
|
return new WrappingAdapter<>(adapter);
|
||||||
return new WrappingAdapter<>(adapter);
|
} else {
|
||||||
}
|
throw new AssertionError("Adapter for unexpected type requested: " + raw);
|
||||||
else {
|
}
|
||||||
throw new AssertionError("Adapter for unexpected type requested: " + raw);
|
}
|
||||||
}
|
})
|
||||||
}
|
.create();
|
||||||
})
|
|
||||||
.create();
|
|
||||||
|
|
||||||
final AtomicReference<TypeAdapter<?>> otherThreadAdapter = new AtomicReference<>();
|
final AtomicReference<TypeAdapter<?>> otherThreadAdapter = new AtomicReference<>();
|
||||||
Thread thread = new Thread() {
|
Thread thread =
|
||||||
@Override
|
new Thread() {
|
||||||
public void run() {
|
@Override
|
||||||
otherThreadAdapter.set(gson.getAdapter(CustomClass1.class));
|
public void run() {
|
||||||
}
|
otherThreadAdapter.set(gson.getAdapter(CustomClass1.class));
|
||||||
};
|
}
|
||||||
|
};
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
// Wait until other thread has obtained FutureAdapter
|
// Wait until other thread has obtained FutureAdapter
|
||||||
|
@ -329,11 +381,12 @@ public final class GsonTest {
|
||||||
DummyAdapter adapter2 = new DummyAdapter(2);
|
DummyAdapter adapter2 = new DummyAdapter(2);
|
||||||
DummyFactory factory2 = new DummyFactory(adapter2);
|
DummyFactory factory2 = new DummyFactory(adapter2);
|
||||||
|
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson =
|
||||||
// Note: This is 'last in, first out' order; Gson will first use factory2, then factory1
|
new GsonBuilder()
|
||||||
.registerTypeAdapterFactory(factory1)
|
// Note: This is 'last in, first out' order; Gson will first use factory2, then factory1
|
||||||
.registerTypeAdapterFactory(factory2)
|
.registerTypeAdapterFactory(factory1)
|
||||||
.create();
|
.registerTypeAdapterFactory(factory2)
|
||||||
|
.create();
|
||||||
|
|
||||||
TypeToken<?> type = TypeToken.get(Number.class);
|
TypeToken<?> type = TypeToken.get(Number.class);
|
||||||
|
|
||||||
|
@ -341,7 +394,8 @@ public final class GsonTest {
|
||||||
assertThrows(NullPointerException.class, () -> gson.getDelegateAdapter(factory1, null));
|
assertThrows(NullPointerException.class, () -> gson.getDelegateAdapter(factory1, null));
|
||||||
|
|
||||||
// For unknown factory the first adapter for that type should be returned
|
// 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(new DummyFactory(new DummyAdapter(0)), type))
|
||||||
|
.isEqualTo(adapter2);
|
||||||
|
|
||||||
assertThat(gson.getDelegateAdapter(factory2, type)).isEqualTo(adapter1);
|
assertThat(gson.getDelegateAdapter(factory2, type)).isEqualTo(adapter1);
|
||||||
// Default Gson adapter should be returned
|
// Default Gson adapter should be returned
|
||||||
|
@ -379,14 +433,15 @@ public final class GsonTest {
|
||||||
@Test
|
@Test
|
||||||
public void testNewJsonWriter_Custom() throws IOException {
|
public void testNewJsonWriter_Custom() throws IOException {
|
||||||
StringWriter writer = new StringWriter();
|
StringWriter writer = new StringWriter();
|
||||||
JsonWriter jsonWriter = new GsonBuilder()
|
JsonWriter jsonWriter =
|
||||||
.disableHtmlEscaping()
|
new GsonBuilder()
|
||||||
.generateNonExecutableJson()
|
.disableHtmlEscaping()
|
||||||
.setPrettyPrinting()
|
.generateNonExecutableJson()
|
||||||
.serializeNulls()
|
.setPrettyPrinting()
|
||||||
.setLenient()
|
.serializeNulls()
|
||||||
.create()
|
.setLenient()
|
||||||
.newJsonWriter(writer);
|
.create()
|
||||||
|
.newJsonWriter(writer);
|
||||||
jsonWriter.beginObject();
|
jsonWriter.beginObject();
|
||||||
jsonWriter.name("test");
|
jsonWriter.name("test");
|
||||||
jsonWriter.nullValue();
|
jsonWriter.nullValue();
|
||||||
|
@ -413,17 +468,15 @@ public final class GsonTest {
|
||||||
@Test
|
@Test
|
||||||
public void testNewJsonReader_Custom() throws IOException {
|
public void testNewJsonReader_Custom() throws IOException {
|
||||||
String json = "test"; // String without quotes
|
String json = "test"; // String without quotes
|
||||||
JsonReader jsonReader = new GsonBuilder()
|
JsonReader jsonReader =
|
||||||
.setLenient()
|
new GsonBuilder().setLenient().create().newJsonReader(new StringReader(json));
|
||||||
.create()
|
|
||||||
.newJsonReader(new StringReader(json));
|
|
||||||
assertThat(jsonReader.nextString()).isEqualTo("test");
|
assertThat(jsonReader.nextString()).isEqualTo("test");
|
||||||
jsonReader.close();
|
jsonReader.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifying a GsonBuilder obtained from {@link Gson#newBuilder()} of a
|
* Modifying a GsonBuilder obtained from {@link Gson#newBuilder()} of a {@code new Gson()} should
|
||||||
* {@code new Gson()} should not affect the Gson instance it came from.
|
* not affect the Gson instance it came from.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultGsonNewBuilderModification() {
|
public void testDefaultGsonNewBuilderModification() {
|
||||||
|
@ -431,25 +484,36 @@ public final class GsonTest {
|
||||||
GsonBuilder gsonBuilder = gson.newBuilder();
|
GsonBuilder gsonBuilder = gson.newBuilder();
|
||||||
|
|
||||||
// Modifications of `gsonBuilder` should not affect `gson` object
|
// Modifications of `gsonBuilder` should not affect `gson` object
|
||||||
gsonBuilder.registerTypeAdapter(CustomClass1.class, new TypeAdapter<CustomClass1>() {
|
gsonBuilder.registerTypeAdapter(
|
||||||
@Override public CustomClass1 read(JsonReader in) throws IOException {
|
CustomClass1.class,
|
||||||
throw new UnsupportedOperationException();
|
new TypeAdapter<CustomClass1>() {
|
||||||
}
|
@Override
|
||||||
|
public CustomClass1 read(JsonReader in) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
@Override
|
||||||
out.value("custom-adapter");
|
public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
||||||
}
|
out.value("custom-adapter");
|
||||||
});
|
}
|
||||||
gsonBuilder.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
|
});
|
||||||
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
gsonBuilder.registerTypeHierarchyAdapter(
|
||||||
return new JsonPrimitive("custom-hierarchy-adapter");
|
CustomClass2.class,
|
||||||
}
|
new JsonSerializer<CustomClass2>() {
|
||||||
});
|
@Override
|
||||||
gsonBuilder.registerTypeAdapter(CustomClass3.class, new InstanceCreator<CustomClass3>() {
|
public JsonElement serialize(
|
||||||
@Override public CustomClass3 createInstance(Type type) {
|
CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
return new CustomClass3("custom-instance");
|
return new JsonPrimitive("custom-hierarchy-adapter");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
gsonBuilder.registerTypeAdapter(
|
||||||
|
CustomClass3.class,
|
||||||
|
new InstanceCreator<CustomClass3>() {
|
||||||
|
@Override
|
||||||
|
public CustomClass3 createInstance(Type type) {
|
||||||
|
return new CustomClass3("custom-instance");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
assertDefaultGson(gson);
|
assertDefaultGson(gson);
|
||||||
// New GsonBuilder created from `gson` should not have been affected by changes either
|
// New GsonBuilder created from `gson` should not have been affected by changes either
|
||||||
|
@ -474,57 +538,79 @@ public final class GsonTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifying a GsonBuilder obtained from {@link Gson#newBuilder()} of a custom
|
* Modifying a GsonBuilder obtained from {@link Gson#newBuilder()} of a custom Gson instance
|
||||||
* Gson instance (created using a GsonBuilder) should not affect the Gson instance
|
* (created using a GsonBuilder) should not affect the Gson instance it came from.
|
||||||
* it came from.
|
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testNewBuilderModification() {
|
public void testNewBuilderModification() {
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson =
|
||||||
.registerTypeAdapter(CustomClass1.class, new TypeAdapter<CustomClass1>() {
|
new GsonBuilder()
|
||||||
@Override public CustomClass1 read(JsonReader in) throws IOException {
|
.registerTypeAdapter(
|
||||||
throw new UnsupportedOperationException();
|
CustomClass1.class,
|
||||||
}
|
new TypeAdapter<CustomClass1>() {
|
||||||
|
@Override
|
||||||
|
public CustomClass1 read(JsonReader in) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
@Override
|
||||||
out.value("custom-adapter");
|
public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
||||||
}
|
out.value("custom-adapter");
|
||||||
})
|
}
|
||||||
.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
|
})
|
||||||
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
.registerTypeHierarchyAdapter(
|
||||||
return new JsonPrimitive("custom-hierarchy-adapter");
|
CustomClass2.class,
|
||||||
}
|
new JsonSerializer<CustomClass2>() {
|
||||||
})
|
@Override
|
||||||
.registerTypeAdapter(CustomClass3.class, new InstanceCreator<CustomClass3>() {
|
public JsonElement serialize(
|
||||||
@Override public CustomClass3 createInstance(Type type) {
|
CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
return new CustomClass3("custom-instance");
|
return new JsonPrimitive("custom-hierarchy-adapter");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.create();
|
.registerTypeAdapter(
|
||||||
|
CustomClass3.class,
|
||||||
|
new InstanceCreator<CustomClass3>() {
|
||||||
|
@Override
|
||||||
|
public CustomClass3 createInstance(Type type) {
|
||||||
|
return new CustomClass3("custom-instance");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
|
||||||
assertCustomGson(gson);
|
assertCustomGson(gson);
|
||||||
|
|
||||||
// Modify `gson.newBuilder()`
|
// Modify `gson.newBuilder()`
|
||||||
GsonBuilder gsonBuilder = gson.newBuilder();
|
GsonBuilder gsonBuilder = gson.newBuilder();
|
||||||
gsonBuilder.registerTypeAdapter(CustomClass1.class, new TypeAdapter<CustomClass1>() {
|
gsonBuilder.registerTypeAdapter(
|
||||||
@Override public CustomClass1 read(JsonReader in) throws IOException {
|
CustomClass1.class,
|
||||||
throw new UnsupportedOperationException();
|
new TypeAdapter<CustomClass1>() {
|
||||||
}
|
@Override
|
||||||
|
public CustomClass1 read(JsonReader in) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
@Override
|
||||||
out.value("overwritten custom-adapter");
|
public void write(JsonWriter out, CustomClass1 value) throws IOException {
|
||||||
}
|
out.value("overwritten custom-adapter");
|
||||||
});
|
}
|
||||||
gsonBuilder.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
|
});
|
||||||
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
gsonBuilder.registerTypeHierarchyAdapter(
|
||||||
return new JsonPrimitive("overwritten custom-hierarchy-adapter");
|
CustomClass2.class,
|
||||||
}
|
new JsonSerializer<CustomClass2>() {
|
||||||
});
|
@Override
|
||||||
gsonBuilder.registerTypeAdapter(CustomClass3.class, new InstanceCreator<CustomClass3>() {
|
public JsonElement serialize(
|
||||||
@Override public CustomClass3 createInstance(Type type) {
|
CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
return new CustomClass3("overwritten custom-instance");
|
return new JsonPrimitive("overwritten custom-hierarchy-adapter");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
gsonBuilder.registerTypeAdapter(
|
||||||
|
CustomClass3.class,
|
||||||
|
new InstanceCreator<CustomClass3>() {
|
||||||
|
@Override
|
||||||
|
public CustomClass3 createInstance(Type type) {
|
||||||
|
return new CustomClass3("overwritten custom-instance");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// `gson` object should not have been affected by changes to new GsonBuilder
|
// `gson` object should not have been affected by changes to new GsonBuilder
|
||||||
assertCustomGson(gson);
|
assertCustomGson(gson);
|
||||||
|
@ -554,8 +640,10 @@ public final class GsonTest {
|
||||||
assertThat(customClass3.s).isEqualTo("custom-instance");
|
assertThat(customClass3.s).isEqualTo("custom-instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CustomClass1 { }
|
private static class CustomClass1 {}
|
||||||
private static class CustomClass2 { }
|
|
||||||
|
private static class CustomClass2 {}
|
||||||
|
|
||||||
private static class CustomClass3 {
|
private static class CustomClass3 {
|
||||||
static final String NO_ARG_CONSTRUCTOR_VALUE = "default instance";
|
static final String NO_ARG_CONSTRUCTOR_VALUE = "default instance";
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue