Merge remote-tracking branch 'origin/master'

# Conflicts:
#	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/internal/bind/ArrayTypeAdapter.java
#	gson/src/main/java/com/google/gson/stream/JsonWriter.java
#	gson/src/test/java/com/google/gson/functional/ArrayTest.java
#	gson/src/test/java/com/google/gson/functional/MapTest.java
#	pom.xml
This commit is contained in:
Johannes Frohnmeyer 2022-09-14 19:10:47 +02:00
commit ce02cb3bc1
Signed by: Johannes
GPG Key ID: E76429612C2929F4
91 changed files with 2869 additions and 943 deletions

1
.gitattributes vendored
View File

@ -1 +0,0 @@
gson/docs/javadocs/* linguist-documentation

View File

@ -7,13 +7,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
cache: 'maven'
- name: Build with Maven
# This also runs javadoc:javadoc to detect any issues with the Javadoc
run: mvn --batch-mode --update-snapshots verify javadoc:javadoc
# This also runs javadoc:jar to detect any issues with the Javadoc generated during release
run: mvn --batch-mode --update-snapshots verify javadoc:jar

View File

@ -0,0 +1,50 @@
name: Check API compatibility
on: pull_request
jobs:
check-api-compatibility:
runs-on: ubuntu-latest
steps:
- name: Checkout old version
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.base.sha }}
path: 'gson-old-japicmp'
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
cache: 'maven'
- name: Build old version
run: |
cd gson-old-japicmp
# Set dummy version
mvn --batch-mode org.codehaus.mojo:versions-maven-plugin:2.11.0:set -DnewVersion=JAPICMP-OLD
# Install artifacts with dummy version in local repository; used later by Maven plugin for comparison
mvn --batch-mode install -DskipTests
- name: Checkout new version
uses: actions/checkout@v3
- name: Check API compatibility
id: check-compatibility
run: |
mvn --batch-mode --fail-at-end package japicmp:cmp -DskipTests
- name: Upload API differences artifacts
uses: actions/upload-artifact@v3
# Run on workflow success (in that case differences report might include added methods and classes)
# or when API compatibility check failed
if: success() || ( failure() && steps.check-compatibility.outcome == 'failure' )
with:
name: api-differences
path: |
**/japicmp/default-cli.html
**/japicmp/default-cli.diff
# Plugin should always have created report files (though they might be empty)
if-no-files-found: error

View File

@ -25,11 +25,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# Run all security queries and maintainability and reliability queries
@ -51,4 +51,4 @@ jobs:
mvn compile --batch-mode
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@ -1,6 +1,20 @@
Change Log
==========
## Version 2.9.1
* Make `Object` and `JsonElement` deserialization iterative rather than
recursive (#1912)
* Added parsing support for enum that has overridden toString() method (#1950)
* Removed support for building Gson with Gradle (#2081)
* Removed obsolete `codegen` hierarchy (#2099)
* Add support for reflection access filter (#1905)
* Improve `TypeToken` creation validation (#2072)
* Add explicit support for `float` in `JsonWriter` (#2130, #2132)
* Fail when parsing invalid local date (#2134)
Also many small improvements to javadoc.
## Version 2.9.0
**The minimum supported Java version changes from 6 to 7.**

View File

@ -45,7 +45,7 @@ There are a few open-source projects that can convert Java objects to JSON. Howe
Gradle:
```gradle
dependencies {
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.google.code.gson:gson:2.9.1'
}
```
@ -54,7 +54,7 @@ Maven:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
<version>2.9.1</version>
</dependency>
```

View File

@ -8,17 +8,18 @@ The following is a step-by-step procedure for releasing a new version of Google-
1. Run `mvn release:clean`
1. Do a dry run: `mvn release:prepare -DdryRun=true`
1. Start the release: `mvn release:prepare`
* Answer questions: usually the defaults are fine.
* This will do a full build, change version from `-SNAPSHOT` to the released version, commit and create the tags. It will then change the version to `-SNAPSHOT` for the next release.
- Answer questions: usually the defaults are fine. Try to follow [Semantic Versioning](https://semver.org/) when choosing the release version number.
- This will do a full build, change version from `-SNAPSHOT` to the released version, commit and create the tags. It will then change the version to `-SNAPSHOT` for the next release.
1. Complete the release: `mvn release:perform`
1. [Log in to Nexus repository manager](https://oss.sonatype.org/index.html#welcome) at Sonatype and close the staging repository for Gson.
1. Download and sanity check all downloads. Do not skip this step! Once you release the staging repository, there is no going back. It will get synced with Maven central and you will not be able to update or delete anything. Your only recourse will be to release a new version of Gson and hope that no one uses the old one.
1. Release the staging repository for Gson. Gson will now get synced to Maven central with-in the next hour. For issues consult [Sonatype Guide](https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide#SonatypeOSSMavenRepositoryUsageGuide-8.ReleaseIt).
1. Update the version in the [Using Gson with Maven2 page](https://github.com/google/gson/blob/master/UserGuide.md#TOC-Gson-With-Maven)
1. Update [Gson Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md). Also, look at all bugs that were fixed and add a few lines describing what changed in the release.
1. Create a post on the [Gson Discussion Forum](https://groups.google.com/group/google-gson)
1. Update the release version in [Wikipedia](https://en.wikipedia.org/wiki/GSON) and update the current "stable" release.
1. Download and sanity check all downloads. Do not skip this step! Once you release the staging repository, there is no going back. It will get synced with Maven Central and you will not be able to update or delete anything. Your only recourse will be to release a new version of Gson and hope that no one uses the old one.
1. Release the staging repository for Gson. Gson will now get synced to Maven Central with-in the next hour. For issues consult [Sonatype Guide](https://central.sonatype.org/publish/release/).
1. Update [Gson Changelog](CHANGELOG.md). Also, look at all bugs that were fixed and add a few lines describing what changed in the release.
1. Update version references in (version might be referenced multiple times):
- [`README.md`](README.md)
- [`UserGuide.md`](UserGuide.md)
1. Optional: Create a post on the [Gson Discussion Forum](https://groups.google.com/group/google-gson).
1. Optional: Update the release version in [Wikipedia](https://en.wikipedia.org/wiki/Gson) and update the current "stable" release.
## Configuring a machine for deployment to Sonatype Repository
@ -31,10 +32,7 @@ This section was borrowed heavily from [Doclava release process](https://code.go
## Getting Maven Publishing Privileges
Based on [Gson group thread](https://groups.google.com/d/topic/google-gson/DHWJHVFpIBg/discussion):
1. [Sign up for a Sonatype account](https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide) following instructions under (2) on that page
1. Ask one of the existing members of the repository to create a JIRA ticket (Step 3 of above document) to add you to the publisher list.
See [OSSRH Publish Guide](https://central.sonatype.org/publish/publish-guide/).
## Running Benchmarks or Tests on Android

View File

@ -14,6 +14,7 @@
* [Array Examples](#TOC-Array-Examples)
* [Collections Examples](#TOC-Collections-Examples)
* [Collections Limitations](#TOC-Collections-Limitations)
* [Maps Examples](#TOC-Maps-Examples)
* [Serializing and Deserializing Generic Types](#TOC-Serializing-and-Deserializing-Generic-Types)
* [Serializing and Deserializing Collection with Objects of Arbitrary Types](#TOC-Serializing-and-Deserializing-Collection-with-Objects-of-Arbitrary-Types)
* [Built-in Serializers and Deserializers](#TOC-Built-in-Serializers-and-Deserializers)
@ -69,30 +70,33 @@ Gson was originally created for use inside Google where it is currently used in
The primary class to use is [`Gson`](gson/src/main/java/com/google/gson/Gson.java) which you can just create by calling `new Gson()`. There is also a class [`GsonBuilder`](gson/src/main/java/com/google/gson/GsonBuilder.java) available that can be used to create a Gson instance with various settings like version control and so on.
The Gson instance does not maintain any state while invoking Json operations. So, you are free to reuse the same object for multiple Json serialization and deserialization operations.
The Gson instance does not maintain any state while invoking JSON operations. So, you are free to reuse the same object for multiple JSON serialization and deserialization operations.
## <a name="TOC-Gson-With-Gradle"></a>Using Gson with Gradle/Android
```
```gradle
dependencies {
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.google.code.gson:gson:2.9.1'
}
```
## <a name="TOC-Gson-With-Maven"></a>Using Gson with Maven
To use Gson with Maven2/3, you can use the Gson version available in Maven Central by adding the following dependency:
```xml
<dependencies>
<!-- Gson: Java to Json conversion -->
<!-- Gson: Java to JSON conversion -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
<version>2.9.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
```
That is it, now your maven project is Gson enabled.
That is it, now your Maven project is Gson enabled.
### <a name="TOC-Primitives-Examples"></a>Primitives Examples
@ -129,7 +133,7 @@ class BagOfPrimitives {
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}
```
@ -151,7 +155,8 @@ BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
* While serializing, a null field is omitted from the output.
* While deserializing, a missing entry in JSON results in setting the corresponding field in the object to its default value: null for object types, zero for numeric types, and false for booleans.
* If a field is _synthetic_, it is ignored and not included in JSON serialization or deserialization.
* Fields corresponding to the outer classes in inner classes, anonymous classes, and local classes are ignored and not included in serialization or deserialization.
* Fields corresponding to the outer classes in inner classes are ignored and not included in serialization or deserialization.
* Anonymous and local classes are excluded. They will be serialized as JSON `null` and when deserialized their JSON value is ignored and `null` is returned. Convert the classes to `static` nested classes to enable serialization and deserialization for them.
### <a name="TOC-Nested-Classes-including-Inner-Classes-"></a>Nested Classes (including Inner Classes)
@ -160,23 +165,23 @@ Gson can serialize static nested classes quite easily.
Gson can also deserialize static nested classes. However, Gson can **not** automatically deserialize the **pure inner classes since their no-args constructor also need a reference to the containing Object** which is not available at the time of deserialization. You can address this problem by either making the inner class static or by providing a custom InstanceCreator for it. Here is an example:
```java
public class A {
public String a;
public class A {
public String a;
class B {
class B {
public String b;
public String b;
public B() {
// No args constructor for B
}
}
}
}
```
**NOTE**: The above class B can not (by default) be serialized with Gson.
Gson can not deserialize `{"b":"abc"}` into an instance of B since the class B is an inner class. If it was defined as static class B then Gson would have been able to deserialize the string. Another solution is to write a custom instance creator for B.
Gson can not deserialize `{"b":"abc"}` into an instance of B since the class B is an inner class. If it was defined as static class B then Gson would have been able to deserialize the string. Another solution is to write a custom instance creator for B.
```java
public class InstanceCreatorForB implements InstanceCreator<A.B> {
@ -204,7 +209,7 @@ gson.toJson(ints); // ==> [1,2,3,4,5]
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
// Deserialization
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
// ==> ints2 will be same as ints
```
@ -214,7 +219,7 @@ We also support multi-dimensional arrays, with arbitrarily complex element types
```java
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
// Serialization
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
@ -233,6 +238,73 @@ Unfortunately, there is no way to get around this in Java.
Gson can serialize collection of arbitrary objects but can not deserialize from it, because there is no way for the user to indicate the type of the resulting object. Instead, while deserializing, the Collection must be of a specific, generic type.
This makes sense, and is rarely a problem when following good Java coding practices.
### <a name="TOC-Maps-Examples"></a>Maps Examples
Gson by default serializes any `java.util.Map` implementation as a JSON object. Because JSON objects only support strings as member names, Gson converts the Map keys to strings by calling `toString()` on them, and using `"null"` for `null` keys:
```java
Gson gson = new Gson();
Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
stringMap.put(null, "null-entry");
// Serialization
String json = gson.toJson(stringMap); // ==> json is {"key":"value","null":"null-entry"}
Map<Integer, Integer> intMap = new LinkedHashMap<>();
intMap.put(2, 4);
intMap.put(3, 6);
// Serialization
String json = gson.toJson(intMap); // ==> json is {"2":4,"3":6}
```
For deserialization Gson uses the `read` method of the `TypeAdapter` registered for the Map key type. Similar to the Collection example shown above, for deserialization a `TypeToken` has to be used to tell Gson what types the Map keys and values have:
```java
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
String json = "{\"key\": \"value\"}";
// Deserialization
Map<String, String> stringMap = gson.fromJson(json, mapType);
// ==> stringMap is {key=value}
```
Gson also supports using complex types as Map keys. This feature can be enabled with [`GsonBuilder.enableComplexMapKeySerialization()`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#enableComplexMapKeySerialization()). If enabled, Gson uses the `write` method of the `TypeAdapter` registered for the Map key type to serialize the keys, instead of using `toString()`. When any of the keys is serialized by the adapter as JSON array or JSON object, Gson will serialize the complete Map as JSON array, consisting of key-value pairs (encoded as JSON array). Otherwise, if none of the keys is serialized as a JSON array or JSON object, Gson will use a JSON object to encode the Map:
```java
class PersonName {
String firstName;
String lastName;
PersonName(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// ... equals and hashCode
}
Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
Map<PersonName, Integer> complexMap = new LinkedHashMap<>();
complexMap.put(new PersonName("John", "Doe"), 30);
complexMap.put(new PersonName("Jane", "Doe"), 35);
// Serialization; complex map is serialized as a JSON array containing key-value pairs (as JSON arrays)
String json = gson.toJson(complexMap);
// ==> json is [[{"firstName":"John","lastName":"Doe"},30],[{"firstName":"Jane","lastName":"Doe"},35]]
Map<String, String> stringMap = new LinkedHashMap<>();
stringMap.put("key", "value");
// Serialization; non-complex map is serialized as a regular JSON object
String json = gson.toJson(stringMap); // json is {"key":"value"}
```
**Important:** Because Gson by default uses `toString()` to serialize Map keys, this can lead to malformed encoded keys or can cause mismatch between serialization and deserialization of the keys, for example when `toString()` is not properly implemented. A workaround for this can be to use `enableComplexMapKeySerialization()` to make sure the `TypeAdapter` registered for the Map key type is used for deserialization _and_ serialization. As shown in the example above, when none of the keys are serialized by the adapter as JSON array or JSON object, the Map is serialized as a regular JSON object, as desired.
Note that when deserializing enums as Map keys, if Gson is unable to find an enum constant with a matching `name()` value respectively `@SerializedName` annotation, it falls back to looking up the enum constant by its `toString()` value. This is to work around the issue described above, but only applies to enum constants.
### <a name="TOC-Serializing-and-Deserializing-Generic-Types"></a>Serializing and Deserializing Generic Types
When you call `toJson(obj)`, Gson calls `obj.getClass()` to get information on the fields to serialize. Similarly, you can typically pass `MyClass.class` object in the `fromJson(json, MyClass.class)` method. This works fine if the object is a non-generic type. However, if the object is of a generic type, then the Generic type information is lost because of Java Type Erasure. Here is an example illustrating the point:
@ -250,7 +322,7 @@ gson.fromJson(json, foo.getClass()); // Fails to deserialize foo.value as Bar
The above code fails to interpret value as type Bar because Gson invokes `foo.getClass()` to get its class information, but this method returns a raw class, `Foo.class`. This means that Gson has no way of knowing that this is an object of type `Foo<Bar>`, and not just plain `Foo`.
You can solve this problem by specifying the correct parameterized type for your generic type. You can do this by using the [`TypeToken`](https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/reflect/TypeToken.html) class.
You can solve this problem by specifying the correct parameterized type for your generic type. You can do this by using the [`TypeToken`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/reflect/TypeToken.html) class.
```java
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
@ -258,6 +330,7 @@ gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
```
The idiom used to get `fooType` actually defines an anonymous local inner class containing a method `getType()` that returns the fully parameterized type.
### <a name="TOC-Serializing-and-Deserializing-Collection-with-Objects-of-Arbitrary-Types"></a>Serializing and Deserializing Collection with Objects of Arbitrary Types
@ -306,7 +379,7 @@ Gson has built-in serializers and deserializers for commonly used classes whose
* `java.net.URL` to match it with strings like `"https://github.com/google/gson/"`
* `java.net.URI` to match it with strings like `"/google/gson/"`
For many more, see the internal class [`TypeAdapters`](https://github.com/google/gson/blob/master/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java).
For many more, see the internal class [`TypeAdapters`](gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java).
You can also find source code for some commonly used classes such as JodaTime at [this page](https://sites.google.com/site/gson/gson-type-adapters-for-common-classes-1).
@ -315,8 +388,8 @@ You can also find source code for some commonly used classes such as JodaTime at
Sometimes 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:
* Json Serializers: Need to define custom serialization for an object
* Json Deserializers: Needed to define custom deserialization for a type
* JSON Serializers: Need to define custom serialization for an object
* JSON Deserializers: Needed to define custom deserialization for a type
* Instance Creators: Not needed if no-args constructor is available or a deserializer is registered
@ -443,10 +516,12 @@ The default JSON output that is provided by Gson is a compact JSON format. This
If you would like to use the Pretty Print feature, you must configure your `Gson` instance using the `GsonBuilder`. The `JsonFormatter` is not exposed through our public API, so the client is unable to configure the default print settings/margins for the JSON output. For now, we only provide a default `JsonPrintFormatter` that has default line length of 80 character, 2 character indentation, and 4 character right margin.
The following is an example shows how to configure a `Gson` instance to use the default `JsonPrintFormatter` instead of the `JsonCompactFormatter`:
```
```java
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);
```
### <a name="TOC-Null-Object-Support"></a>Null Object Support
The default behaviour that is implemented in Gson is that `null` object fields are ignored. This allows for a more compact output format; however, the client must define a default value for these fields as the JSON format is converted back into its Java form.
@ -557,7 +632,7 @@ This feature provides a way where you can mark certain fields of your objects to
#### <a name="TOC-User-Defined-Exclusion-Strategies"></a>User Defined Exclusion Strategies
If the above mechanisms for excluding fields and class type do not work for you then you can always write your own exclusion strategy and plug it into Gson. See the [`ExclusionStrategy`](https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/ExclusionStrategy.html) JavaDoc for more information.
If the above mechanisms for excluding fields and class type do not work for you then you can always write your own exclusion strategy and plug it into Gson. See the [`ExclusionStrategy`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/ExclusionStrategy.html) JavaDoc for more information.
The following example shows how to exclude fields marked with a specific `@Foo` annotation and excludes top-level types (or declared field type) of class `String`.
@ -610,13 +685,13 @@ public static void main(String[] args) {
The output is:
```
```json
{"longField":1234}
```
### <a name="TOC-JSON-Field-Naming-Support"></a>JSON Field Naming Support
Gson supports some pre-defined field naming policies to convert the standard Java field names (i.e., camel cased names starting with lower case --- `sampleFieldNameInJava`) to a Json field name (i.e., `sample_field_name_in_java` or `SampleFieldNameInJava`). See the [FieldNamingPolicy](https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/FieldNamingPolicy.html) class for information on the pre-defined naming policies.
Gson supports some pre-defined field naming policies to convert the standard Java field names (i.e., camel cased names starting with lower case --- `sampleFieldNameInJava`) to a JSON field name (i.e., `sample_field_name_in_java` or `SampleFieldNameInJava`). See the [FieldNamingPolicy](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/FieldNamingPolicy.html) class for information on the pre-defined naming policies.
It also has an annotation based strategy to allows clients to define custom names on a per field basis. Note, that the annotation based strategy has field name validation which will raise "Runtime" exceptions if an invalid field name is provided as the annotation value.
@ -641,11 +716,11 @@ System.out.println(jsonRepresentation);
The output is:
```
```json
{"custom_naming":"first","SomeOtherField":"second"}
```
If you have a need for custom naming policy ([see this discussion](https://groups.google.com/group/google-gson/browse_thread/thread/cb441a2d717f6892)), you can use the [@SerializedName](https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/annotations/SerializedName.html) annotation.
If you have a need for custom naming policy ([see this discussion](https://groups.google.com/group/google-gson/browse_thread/thread/cb441a2d717f6892)), you can use the [@SerializedName](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/SerializedName.html) annotation.
### <a name="TOC-Sharing-State-Across-Custom-Serializers-and-Deserializers"></a>Sharing State Across Custom Serializers and Deserializers
@ -663,7 +738,7 @@ In addition Gson's object model and data binding, you can use Gson to read from
## <a name="TOC-Issues-in-Designing-Gson"></a>Issues in Designing Gson
See the [Gson design document](https://github.com/google/gson/blob/master/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 include a comparison of Gson with other Java libraries that can be used for JSON conversion.
## <a name="TOC-Future-Enhancements-to-Gson"></a>Future Enhancements to Gson

View File

@ -3,7 +3,7 @@
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.9.1-SNAPSHOT</version>
<version>2.9.2-SNAPSHOT</version>
</parent>
<artifactId>gson-extras</artifactId>
@ -47,7 +47,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M2</version>
<configuration>
<!-- Currently not deployed -->
<skip>true</skip>

View File

@ -42,7 +42,6 @@ import java.util.Queue;
* Writes a graph of objects as a list of named nodes.
*/
// TODO: proper documentation
@SuppressWarnings("rawtypes")
public final class GraphAdapterBuilder {
private final Map<Type, InstanceCreator<?>> instanceCreators;
private final ConstructorConstructor constructorConstructor;
@ -78,7 +77,7 @@ public final class GraphAdapterBuilder {
}
}
static class Factory implements TypeAdapterFactory, InstanceCreator {
static class Factory implements TypeAdapterFactory, InstanceCreator<Object> {
private final Map<Type, InstanceCreator<?>> instanceCreators;
private final ThreadLocal<Graph> graphThreadLocal = new ThreadLocal<>();
@ -215,7 +214,6 @@ public final class GraphAdapterBuilder {
* <p>Gson should only ever call this method when we're expecting it to;
* that is only when we've called back into Gson to deserialize a tree.
*/
@SuppressWarnings("unchecked")
@Override
public Object createInstance(Type type) {
Graph graph = graphThreadLocal.get();
@ -242,14 +240,14 @@ public final class GraphAdapterBuilder {
* The queue of elements to write during serialization. Unused during
* deserialization.
*/
private final Queue<Element> queue = new LinkedList<>();
private final Queue<Element<?>> queue = new LinkedList<>();
/**
* The instance currently being deserialized. Used as a backdoor between
* the graph traversal (which needs to know instances) and instance creators
* which create them.
*/
private Element nextCreate;
private Element<Object> nextCreate;
private Graph(Map<Object, Element<?>> map) {
this.map = map;
@ -299,11 +297,12 @@ public final class GraphAdapterBuilder {
typeAdapter.write(out, value);
}
@SuppressWarnings("unchecked")
void read(Graph graph) throws IOException {
if (graph.nextCreate != null) {
throw new IllegalStateException("Unexpected recursive call to read() for " + id);
}
graph.nextCreate = this;
graph.nextCreate = (Element<Object>) this;
value = typeAdapter.fromJsonTree(element);
if (value == null) {
throw new IllegalStateException("non-null value deserialized to null: " + element);

View File

@ -16,10 +16,6 @@
package com.google.gson.typeadapters;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ -30,6 +26,9 @@ import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Adapts values whose runtime type may differ from their declaration type. This
@ -138,8 +137,10 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
private final boolean maintainType;
private boolean recognizeSubtypes;
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
private RuntimeTypeAdapterFactory(
Class<?> baseType, String typeFieldName, boolean maintainType) {
if (typeFieldName == null || baseType == null) {
throw new NullPointerException();
}
@ -151,7 +152,8 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
* {@code maintainType} flag decide if the type will be stored in pojo or not.
*
* @param maintainType true if the type field should be included in deserialized objects
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
@ -173,6 +175,15 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
}
/**
* Ensures that this factory will handle not just the given {@code baseType}, but any subtype
* of that type.
*/
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
this.recognizeSubtypes = true;
return this;
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
@ -205,7 +216,13 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
@Override
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
if (type.getRawType() != baseType) {
if (type == null) {
return null;
}
Class<?> rawType = type.getRawType();
boolean handle =
recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
if (!handle) {
return null;
}

View File

@ -16,15 +16,12 @@
package com.google.gson.typeadapters;
import javax.annotation.PostConstruct;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import junit.framework.TestCase;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import junit.framework.TestCase;
public class PostConstructAdapterFactoryTest extends TestCase {
public void test() throws Exception {
@ -55,6 +52,7 @@ public class PostConstructAdapterFactoryTest extends TestCase {
assertEquals(sandwiches, sandwichesFromJson);
}
@SuppressWarnings("overrides") // for missing hashCode() override
static class Sandwich {
public String bread;
public String cheese;
@ -89,6 +87,7 @@ public class PostConstructAdapterFactoryTest extends TestCase {
}
}
@SuppressWarnings("overrides") // for missing hashCode() override
static class MultipleSandwiches {
public List<Sandwich> sandwiches;

View File

@ -42,6 +42,27 @@ public final class RuntimeTypeAdapterFactoryTest extends TestCase {
assertTrue(deserialized instanceof CreditCard);
}
public void testRuntimeTypeAdapterRecognizeSubtypes() {
// We don't have an explicit factory for CreditCard.class, but we do have one for
// BillingInstrument.class that has recognizeSubtypes(). So it should recognize CreditCard, and
// when we call gson.toJson(original) below, without an explicit type, it should be invoked.
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)
.recognizeSubtypes()
.registerSubtype(CreditCard.class);
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(rta)
.create();
CreditCard original = new CreditCard("Jesse", 234);
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
gson.toJson(original));
BillingInstrument deserialized = gson.fromJson(
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
assertTrue(deserialized instanceof CreditCard);
}
public void testRuntimeTypeIsBaseType() {
TypeAdapterFactory rta = RuntimeTypeAdapterFactory.of(
BillingInstrument.class)

View File

@ -4,7 +4,7 @@
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.9.1-SNAPSHOT</version>
<version>2.9.2-SNAPSHOT</version>
</parent>
<artifactId>gson</artifactId>
@ -99,7 +99,7 @@
<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
<version>6.2.0</version>
<version>6.3.1</version>
<executions>
<execution>
<goals>

View File

@ -17,11 +17,8 @@
package com.google.gson;
/**
* A strategy (or policy) definition that is used to decide whether or not a field or top-level
* class should be serialized or deserialized as part of the JSON output/input. For serialization,
* if the {@link #shouldSkipClass(Class)} method returns true then that class or field type
* will not be part of the JSON output. For deserialization, if {@link #shouldSkipClass(Class)}
* returns true, then it will not be set as part of the Java object structure.
* A strategy (or policy) definition that is used to decide whether or not a field or
* class should 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.
*
@ -64,7 +61,7 @@ package com.google.gson;
*
* <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then
* the {@code GsonBuilder} is required. The following is an example of how you can use the
* {@code GsonBuilder} to configure Gson to use one of the above sample:
* {@code GsonBuilder} to configure Gson to use one of the above samples:
* <pre class="code">
* ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
* Gson gson = new GsonBuilder()

View File

@ -16,12 +16,12 @@
package com.google.gson;
import com.google.gson.internal.$Gson$Preconditions;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
/**
* A data object that stores attributes of a field.
@ -42,8 +42,7 @@ public final class FieldAttributes {
* @param f the field to pull attributes from
*/
public FieldAttributes(Field f) {
$Gson$Preconditions.checkNotNull(f);
this.field = f;
this.field = Objects.requireNonNull(f);
}
/**
@ -135,27 +134,8 @@ public final class FieldAttributes {
return (field.getModifiers() & modifier) != 0;
}
/**
* Returns the value of the field represented by this {@code Field}, on
* the specified object. The value is automatically wrapped in an
* object if it has a primitive type.
*
* @return the value of the represented field in object
* {@code obj}; primitive values are wrapped in an appropriate
* object before being returned
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
Object get(Object instance) throws IllegalAccessException {
return field.get(instance);
}
/**
* This is exposed internally only for the removing synthetic fields from the JSON output.
*
* @return true if the field is synthetic; otherwise false
*/
boolean isSynthetic() {
return field.isSynthetic();
@Override
public String toString() {
return field.toString();
}
}

View File

@ -16,25 +16,6 @@
package com.google.gson;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
import com.google.gson.internal.*;
import com.google.gson.internal.bind.ArrayTypeAdapter;
import com.google.gson.internal.bind.CollectionTypeAdapterFactory;
@ -53,6 +34,26 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;
/**
* This is the main class for using Gson. Gson is typically used by first constructing a
@ -92,6 +93,33 @@ import com.google.gson.stream.MalformedJsonException;
* <p>See the <a href="https://sites.google.com/site/gson/gson-user-guide">Gson User Guide</a>
* for a more complete set of examples.</p>
*
* <h2>Lenient JSON handling</h2>
* For legacy reasons most of the {@code Gson} methods allow JSON data which does not
* comply with the JSON specification, regardless of whether {@link GsonBuilder#setLenient()}
* is used or not. If this behavior is not desired, the following workarounds can be used:
*
* <h3>Serialization</h3>
* <ol>
* <li>Use {@link #getAdapter(Class)} to obtain the adapter for the type to be serialized
* <li>When using an existing {@code JsonWriter}, manually apply the writer settings of this
* {@code Gson} instance listed by {@link #newJsonWriter(Writer)}.<br>
* Otherwise, when not using an existing {@code JsonWriter}, use {@link #newJsonWriter(Writer)}
* to construct one.
* <li>Call {@link TypeAdapter#write(JsonWriter, Object)}
* </ol>
*
* <h3>Deserialization</h3>
* <ol>
* <li>Use {@link #getAdapter(Class)} to obtain the adapter for the type to be deserialized
* <li>When using an existing {@code JsonReader}, manually apply the reader settings of this
* {@code Gson} instance listed by {@link #newJsonReader(Reader)}.<br>
* Otherwise, when not using an existing {@code JsonReader}, use {@link #newJsonReader(Reader)}
* to construct one.
* <li>Call {@link TypeAdapter#read(JsonReader)}
* <li>Call {@link JsonReader#peek()} and verify that the result is {@link JsonToken#END_DOCUMENT}
* to make sure there is no trailing data
* </ol>
*
* @see com.google.gson.reflect.TypeToken
*
* @author Inderjeet Singh
@ -109,7 +137,7 @@ public final class Gson {
private final ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
= new ThreadLocal<>();
private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<>();
private final ConcurrentMap<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<>();
private final ConstructorConstructor constructorConstructor;
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
@ -459,11 +487,13 @@ public final class Gson {
* @throws IllegalArgumentException if this GSON cannot serialize and
* deserialize {@code type}.
*/
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
TypeAdapter<?> cached = typeTokenCache.get(type == null ? DefaultConfig.NULL_KEY_SURROGATE : type);
Objects.requireNonNull(type, "type must not be null");
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {
return (TypeAdapter<T>) cached;
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) cached;
return adapter;
}
Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
@ -475,6 +505,7 @@ public final class Gson {
}
// the key and value type parameters always agree
@SuppressWarnings("unchecked")
FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
@ -487,8 +518,14 @@ public final class Gson {
for (TypeAdapterFactory factory : factories) {
TypeAdapter<T> candidate = factory.create(this, type);
if (candidate != null) {
@SuppressWarnings("unchecked")
TypeAdapter<T> existingAdapter = (TypeAdapter<T>) typeTokenCache.putIfAbsent(type, candidate);
// If other thread concurrently added adapter prefer that one instead
if (existingAdapter != null) {
candidate = existingAdapter;
}
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
@ -719,11 +756,20 @@ public final class Gson {
/**
* Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
* {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @throws JsonIOException if there was a problem writing to the writer
*/
@SuppressWarnings("unchecked")
public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
@SuppressWarnings("unchecked")
TypeAdapter<Object> adapter = (TypeAdapter<Object>) getAdapter(TypeToken.get(typeOfSrc));
boolean oldLenient = writer.isLenient();
writer.setLenient(lenient);
boolean oldOmitQuotes = writer.getOmitQuotes();
@ -733,7 +779,7 @@ public final class Gson {
boolean oldSerializeNulls = writer.getSerializeNulls();
writer.setSerializeNulls(serializeNulls);
try {
((TypeAdapter<Object>) adapter).write(writer, src);
adapter.write(writer, src);
} catch (IOException e) {
throw new JsonIOException(e);
} catch (AssertionError e) {
@ -822,6 +868,15 @@ public final class Gson {
/**
* Writes the JSON for {@code jsonElement} to {@code writer}.
*
* <p>The JSON data is written in {@linkplain JsonWriter#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided writer. The lenient mode setting
* of the writer is restored once this method returns.
*
* <p>The 'HTML-safe' and 'serialize {@code null}' settings of this {@code Gson} instance
* (configured by the {@link GsonBuilder}) are applied, and the original settings of the
* writer are restored once this method returns.
*
* @throws JsonIOException if there was a problem writing to the writer
*/
public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
@ -859,6 +914,9 @@ public final class Gson {
* {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of
* a String, use {@link #fromJson(Reader, Class)} instead.
*
* <p>An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the string from which the object is to be deserialized
* @param classOfT the class of T
@ -878,6 +936,9 @@ public final class Gson {
* {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of
* a String, use {@link #fromJson(Reader, Type)} instead.
*
* <p>An exception is thrown if the JSON string has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the string from which the object is to be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
@ -891,12 +952,12 @@ public final class Gson {
* @throws JsonParseException if json is not a valid representation for an object of type typeOfT
* @throws JsonSyntaxException if json is not a valid representation for an object of type
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
if (json == null) {
return null;
}
StringReader reader = new StringReader(json);
@SuppressWarnings("unchecked")
T target = (T) fromJson(reader, typeOfT);
return target;
}
@ -911,6 +972,9 @@ public final class Gson {
* invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a
* {@link Reader}, use {@link #fromJson(String, Class)} instead.
*
* <p>An exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the reader producing the Json from which the object is to be deserialized.
* @param classOfT the class of T
@ -932,6 +996,9 @@ public final class Gson {
* non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a
* String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead.
*
* <p>An exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @param <T> the type of the desired object
* @param json the reader producing Json from which the object is to be deserialized
* @param typeOfT The specific genericized type of src. You can obtain this type by using the
@ -945,9 +1012,9 @@ public final class Gson {
* @throws JsonSyntaxException if json is not a valid representation for an object of type
* @since 1.2
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
JsonReader jsonReader = newJsonReader(json);
@SuppressWarnings("unchecked")
T object = (T) fromJson(jsonReader, typeOfT);
assertFullConsumption(object, jsonReader);
return object;
@ -956,7 +1023,7 @@ public final class Gson {
private static void assertFullConsumption(Object obj, JsonReader reader) {
try {
if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
throw new JsonSyntaxException("JSON document was not fully consumed.");
}
} catch (MalformedJsonException e) {
throw new JsonSyntaxException(e);
@ -968,12 +1035,18 @@ public final class Gson {
/**
* Reads the next JSON value from {@code reader} and convert it to an object
* of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF.
* Since Type is not parameterized by T, this method is type unsafe and should be used carefully
* Since Type is not parameterized by T, this method is type unsafe and should be used carefully.
*
* <p>Unlike the other {@code fromJson} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
*
* @throws JsonIOException if there was a problem writing to the Reader
* @throws JsonSyntaxException if json is not a valid representation for an object of type
*/
@SuppressWarnings("unchecked")
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
@ -981,6 +1054,7 @@ public final class Gson {
try {
reader.peek();
isEmpty = false;
@SuppressWarnings("unchecked")
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);

View File

@ -16,15 +16,7 @@
package com.google.gson;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static com.google.gson.internal.DefaultConfig.*;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Excluder;
@ -34,8 +26,17 @@ import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.internal.sql.SqlTypesSupport;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import static com.google.gson.internal.DefaultConfig.*;
import com.google.gson.stream.JsonWriter;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
@ -149,8 +150,11 @@ public final class GsonBuilder {
/**
* Configures Gson to excludes all class fields that have the specified modifiers. By default,
* Gson will exclude all fields marked transient or static. This method will override that
* behavior.
* Gson will exclude all fields marked {@code transient} or {@code static}. This method will
* override that behavior.
*
* <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}.
*
* @param modifiers the field modifiers. You must use the modifiers specified in the
* {@link java.lang.reflect.Modifier} class. For example,
@ -159,6 +163,7 @@ public final class GsonBuilder {
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder excludeFieldsWithModifiers(int... modifiers) {
Objects.requireNonNull(modifiers);
excluder = excluder.withModifiers(modifiers);
return this;
}
@ -178,9 +183,12 @@ public final class GsonBuilder {
}
/**
* Configures Gson to exclude all fields from consideration for serialization or deserialization
* Configures Gson to exclude all fields from consideration for serialization and deserialization
* 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
* these fields was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder excludeFieldsWithoutExposeAnnotation() {
@ -299,7 +307,20 @@ public final class GsonBuilder {
}
/**
* Configures Gson to exclude inner classes during serialization.
* Configures Gson to exclude inner classes (= non-{@code static} nested classes) during serialization
* and deserialization. This is a convenience method which behaves as if an {@link ExclusionStrategy}
* which excludes inner classes was {@linkplain #setExclusionStrategies(ExclusionStrategy...) registered with this builder}.
* This means inner classes will be serialized as JSON {@code null}, and will be deserialized as
* Java {@code null} 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
* enclosing instance. Deserialization might not be possible at all when {@link #disableJdkUnsafe()}
* is used (and no custom {@link InstanceCreator} is registered), or it can 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}
* nested classes if possible.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.3
@ -318,33 +339,30 @@ public final class GsonBuilder {
* @since 1.3
*/
public GsonBuilder setLongSerializationPolicy(LongSerializationPolicy serializationPolicy) {
this.longSerializationPolicy = serializationPolicy;
this.longSerializationPolicy = Objects.requireNonNull(serializationPolicy);
return this;
}
/**
* Configures Gson to apply a specific naming policy to an object's field during serialization
* Configures Gson to apply a specific naming policy to an object's fields during serialization
* and deserialization.
*
* @param namingConvention the JSON field naming convention to use for serialization and
* deserialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* <p>This method just delegates to {@link #setFieldNamingStrategy(FieldNamingStrategy)}.
*/
public GsonBuilder setFieldNamingPolicy(FieldNamingPolicy namingConvention) {
this.fieldNamingPolicy = namingConvention;
return this;
return setFieldNamingStrategy(namingConvention);
}
/**
* Configures Gson to apply a specific naming policy strategy to an object's field during
* Configures Gson to apply a specific naming strategy to an object's fields during
* serialization and deserialization.
*
* @param fieldNamingStrategy the actual 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
* @since 1.3
*/
public GsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) {
this.fieldNamingPolicy = fieldNamingStrategy;
this.fieldNamingPolicy = Objects.requireNonNull(fieldNamingStrategy);
return this;
}
@ -356,7 +374,7 @@ public final class GsonBuilder {
* @see ToNumberPolicy#DOUBLE The default object-to-number strategy
*/
public GsonBuilder setObjectToNumberStrategy(ToNumberStrategy objectToNumberStrategy) {
this.objectToNumberStrategy = objectToNumberStrategy;
this.objectToNumberStrategy = Objects.requireNonNull(objectToNumberStrategy);
return this;
}
@ -368,7 +386,7 @@ public final class GsonBuilder {
* @see ToNumberPolicy#LAZILY_PARSED_NUMBER The default number-to-number strategy
*/
public GsonBuilder setNumberToNumberStrategy(ToNumberStrategy numberToNumberStrategy) {
this.numberToNumberStrategy = numberToNumberStrategy;
this.numberToNumberStrategy = Objects.requireNonNull(numberToNumberStrategy);
return this;
}
@ -377,12 +395,25 @@ public final class GsonBuilder {
* deserialization. Each of the {@code strategies} will be applied as a disjunction rule.
* This means that if one of the {@code strategies} suggests that a field (or class) should be
* skipped then that field (or object) is skipped during serialization/deserialization.
* The strategies are added to the existing strategies (if any); the existing strategies
* are not replaced.
*
* <p>Fields are excluded for serialization and deserialization when
* {@link ExclusionStrategy#shouldSkipField(FieldAttributes) shouldSkipField} returns {@code true},
* or when {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass} returns {@code true}
* for the field type. Gson behaves as if the field did not exist; its value is not serialized
* and on deserialization if a JSON member with this name exists it is skipped by default.<br>
* When objects of an excluded type (as determined by
* {@link ExclusionStrategy#shouldSkipClass(Class) shouldSkipClass}) are serialized a
* JSON null is written to output, and when deserialized the JSON value is skipped and
* {@code null} is returned.
*
* @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
* @since 1.4
*/
public GsonBuilder setExclusionStrategies(ExclusionStrategy... strategies) {
Objects.requireNonNull(strategies);
for (ExclusionStrategy strategy : strategies) {
excluder = excluder.withExclusionStrategy(strategy, true, true);
}
@ -397,11 +428,15 @@ public final class GsonBuilder {
* class) should be skipped then that field (or object) is skipped during its
* serialization.
*
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)}
* for a detailed description of the effect of exclusion strategies.
*
* @param strategy an exclusion strategy to apply during serialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public GsonBuilder addSerializationExclusionStrategy(ExclusionStrategy strategy) {
Objects.requireNonNull(strategy);
excluder = excluder.withExclusionStrategy(strategy, true, false);
return this;
}
@ -414,11 +449,15 @@ public final class GsonBuilder {
* class) should be skipped then that field (or object) is skipped during its
* deserialization.
*
* <p>See the documentation of {@link #setExclusionStrategies(ExclusionStrategy...)}
* for a detailed description of the effect of exclusion strategies.
*
* @param strategy an exclusion strategy to apply during deserialization.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
public GsonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strategy) {
Objects.requireNonNull(strategy);
excluder = excluder.withExclusionStrategy(strategy, false, true);
return this;
}
@ -435,12 +474,14 @@ public final class GsonBuilder {
}
/**
* By default, Gson is strict and only accepts JSON as specified by
* <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. This option makes the parser
* liberal in what it accepts.
* Configures Gson to allow JSON data which does not strictly comply with the JSON specification.
*
* <p>Note: Due to legacy reasons most methods of Gson are always lenient, regardless of
* whether this builder method is used.
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @see JsonReader#setLenient(boolean)
* @see JsonWriter#setLenient(boolean)
*/
public GsonBuilder setLenient() {
lenient = true;
@ -543,26 +584,34 @@ public final class GsonBuilder {
* types! For example, applications registering {@code boolean.class} should also register {@code
* Boolean.class}.
*
* <p>{@link JsonSerializer} and {@link JsonDeserializer} are made "{@code null}-safe". This
* means when trying to serialize {@code null}, Gson will write a JSON {@code null} and the
* serializer is not called. Similarly when deserializing a JSON {@code null}, Gson will emit
* {@code null} without calling the deserializer. If it is desired to handle {@code null} values,
* a {@link TypeAdapter} should be used instead.
*
* @param type the type definition for the type adapter being registered
* @param typeAdapter This object must implement at least one of the {@link TypeAdapter},
* {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
Objects.requireNonNull(type);
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof InstanceCreator<?>
|| typeAdapter instanceof TypeAdapter<?>);
if (typeAdapter instanceof InstanceCreator<?>) {
instanceCreators.put(type, (InstanceCreator) typeAdapter);
instanceCreators.put(type, (InstanceCreator<?>) typeAdapter);
}
if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
TypeToken<?> typeToken = TypeToken.get(type);
factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter<?>) {
factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
@SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapterFactory factory = TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter);
factories.add(factory);
}
return this;
}
@ -576,6 +625,7 @@ public final class GsonBuilder {
* @since 2.1
*/
public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
Objects.requireNonNull(factory);
factories.add(factory);
return this;
}
@ -594,8 +644,8 @@ public final class GsonBuilder {
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
* @since 1.7
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
Objects.requireNonNull(baseType);
$Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
|| typeAdapter instanceof JsonDeserializer<?>
|| typeAdapter instanceof TypeAdapter<?>);
@ -603,7 +653,9 @@ public final class GsonBuilder {
hierarchyFactories.add(TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
}
if (typeAdapter instanceof TypeAdapter<?>) {
factories.add(TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter));
@SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapterFactory factory = TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter);
factories.add(factory);
}
return this;
}
@ -669,8 +721,7 @@ public final class GsonBuilder {
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
public GsonBuilder addReflectionAccessFilter(ReflectionAccessFilter filter) {
if (filter == null) throw new NullPointerException();
Objects.requireNonNull(filter);
reflectionFilters.addFirst(filter);
return this;
}

View File

@ -23,9 +23,10 @@ import java.util.Iterator;
import java.util.List;
/**
* A class representing an array type in Json. An array is a list of {@link JsonElement}s each of
* A class representing an array type in JSON. An array is a list of {@link JsonElement}s each of
* which can be of a different type. This is an ordered list, meaning that the order in which
* elements are added is preserved.
* elements are added is preserved. This class does not support {@code null} values. If {@code null}
* is provided as element argument to any of the methods, it is converted to a {@link JsonNull}.
*
* @author Inderjeet Singh
* @author Joel Leitch
@ -36,16 +37,26 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
/**
* Creates an empty JsonArray.
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonArray() {
elements = new ArrayList<>();
}
/**
* Creates an empty JsonArray with the desired initial capacity.
*
* @param capacity initial capacity.
* @throws IllegalArgumentException if the {@code capacity} is
* negative
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonArray(int capacity) {
elements = new ArrayList<>(capacity);
}
/**
* Creates a deep copy of this element and all its children
* Creates a deep copy of this element and all its children.
*
* @since 2.8.2
*/
@Override
@ -119,19 +130,20 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
/**
* Replaces the element at the specified position in this array with the specified element.
* Element can be null.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException if the specified index is outside the array bounds
*/
public JsonElement set(int index, JsonElement element) {
return elements.set(index, element);
return elements.set(index, element == null ? JsonNull.INSTANCE : element);
}
/**
* Removes the first occurrence of the specified element from this array, if it is present.
* If the array does not contain the element, it is unchanged.
*
* @param element element to be removed from this array, if present
* @return true if this array contained the specified element, false otherwise
* @since 2.3
@ -144,6 +156,7 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
* Removes the element at the specified position in this array. Shifts any subsequent elements
* to the left (subtracts one from their indices). Returns the element that was removed from
* the array.
*
* @param index index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException if the specified index is outside the array bounds
@ -155,6 +168,7 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
/**
* Returns true if this array contains the specified element.
*
* @return true if this array contains the specified element.
* @param element whose presence in this array is to be tested
* @since 2.3
@ -171,11 +185,11 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
public int size() {
return elements.size();
}
/**
* Returns true if the array is empty
* Returns true if the array is empty.
*
* @return true if the array is empty
* @return true if the array is empty.
*/
public boolean isEmpty() {
return elements.isEmpty();
@ -193,10 +207,10 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
}
/**
* Returns the ith element of the array.
* Returns the i-th element of the array.
*
* @param i the index of the element that is being sought.
* @return the element present at the ith index.
* @return the element present at the i-th index.
* @throws IndexOutOfBoundsException if i is negative or greater than or equal to the
* {@link #size()} of the array.
*/
@ -204,191 +218,189 @@ public final class JsonArray extends JsonElement implements Iterable<JsonElement
return elements.get(i);
}
private JsonElement getAsSingleElement() {
int size = elements.size();
if (size == 1) {
return elements.get(0);
}
throw new IllegalStateException("Array must have size 1, but has size " + size);
}
/**
* 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 method calls {@link JsonElement#getAsNumber()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a number if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid Number.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public Number getAsNumber() {
if (elements.size() == 1) {
return elements.get(0).getAsNumber();
}
throw new IllegalStateException();
return getAsSingleElement().getAsNumber();
}
/**
* 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 method calls {@link JsonElement#getAsString()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a String if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid String.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public String getAsString() {
if (elements.size() == 1) {
return elements.get(0).getAsString();
}
throw new IllegalStateException();
return getAsSingleElement().getAsString();
}
/**
* 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 calls {@link JsonElement#getAsDouble()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a double if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid double.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public double getAsDouble() {
if (elements.size() == 1) {
return elements.get(0).getAsDouble();
}
throw new IllegalStateException();
return getAsSingleElement().getAsDouble();
}
/**
* 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
* of the exceptions declared by that method can occur.
*
* @return get this element as a {@link BigDecimal} if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}.
* @throws NumberFormatException if the element at index 0 is not a valid {@link BigDecimal}.
* @throws IllegalStateException if the array has more than one element.
* @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.
* @since 1.2
*/
@Override
public BigDecimal getAsBigDecimal() {
if (elements.size() == 1) {
return elements.get(0).getAsBigDecimal();
}
throw new IllegalStateException();
return getAsSingleElement().getAsBigDecimal();
}
/**
* 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
* of the exceptions declared by that method can occur.
*
* @return get this element as a {@link BigInteger} if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}.
* @throws NumberFormatException if the element at index 0 is not a valid {@link BigInteger}.
* @throws IllegalStateException if the array has more than one element.
* @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.
* @since 1.2
*/
@Override
public BigInteger getAsBigInteger() {
if (elements.size() == 1) {
return elements.get(0).getAsBigInteger();
}
throw new IllegalStateException();
return getAsSingleElement().getAsBigInteger();
}
/**
* 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 calls {@link JsonElement#getAsFloat()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a float if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid float.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public float getAsFloat() {
if (elements.size() == 1) {
return elements.get(0).getAsFloat();
}
throw new IllegalStateException();
return getAsSingleElement().getAsFloat();
}
/**
* 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 calls {@link JsonElement#getAsLong()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a long if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid long.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public long getAsLong() {
if (elements.size() == 1) {
return elements.get(0).getAsLong();
}
throw new IllegalStateException();
return getAsSingleElement().getAsLong();
}
/**
* 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 calls {@link JsonElement#getAsInt()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as an integer if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid integer.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public int getAsInt() {
if (elements.size() == 1) {
return elements.get(0).getAsInt();
}
throw new IllegalStateException();
return getAsSingleElement().getAsInt();
}
/**
* Convenience method to get this array as a primitive byte if it contains a single element.
* This method calls {@link JsonElement#getAsByte()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @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.
*/
@Override
public byte getAsByte() {
if (elements.size() == 1) {
return elements.get(0).getAsByte();
}
throw new IllegalStateException();
return getAsSingleElement().getAsByte();
}
/**
* Convenience method to get this array as a character if it contains a single element.
* This method calls {@link JsonElement#getAsCharacter()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @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.
* @deprecated This method is misleading, as it does not get this element as a char but rather as
* a string's first character.
*/
@Deprecated
@Override
public char getAsCharacter() {
if (elements.size() == 1) {
JsonElement element = elements.get(0);
return element.getAsCharacter();
}
throw new IllegalStateException();
return getAsSingleElement().getAsCharacter();
}
/**
* 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 method calls {@link JsonElement#getAsShort()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a primitive short if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid short.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public short getAsShort() {
if (elements.size() == 1) {
return elements.get(0).getAsShort();
}
throw new IllegalStateException();
return getAsSingleElement().getAsShort();
}
/**
* 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 calls {@link JsonElement#getAsBoolean()} on the element, therefore any
* of the exceptions declared by that method can occur.
*
* @return get this element as a boolean if it is single element array.
* @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
* is not a valid boolean.
* @throws IllegalStateException if the array has more than one element.
* @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.
*/
@Override
public boolean getAsBoolean() {
if (elements.size() == 1) {
return elements.get(0).getAsBoolean();
}
throw new IllegalStateException();
return getAsSingleElement().getAsBoolean();
}
/**
* Returns whether the other object is equal to this. This method only considers
* the other object to be equal if it is an instance of {@code JsonArray} and has
* equal elements in the same order.
*/
@Override
public boolean equals(Object o) {
return (o == this) || (o instanceof JsonArray && ((JsonArray) o).elements.equals(elements));
}
/**
* Returns the hash code of this array. This method calculates the hash code based
* on the elements of this array.
*/
@Override
public int hashCode() {
return elements.hashCode();

View File

@ -19,7 +19,7 @@ package com.google.gson;
import java.lang.reflect.Type;
/**
* <p>Interface representing a custom deserializer for Json. You should write a custom
* <p>Interface representing a custom deserializer for JSON. You should write a custom
* deserializer, if you are not happy with the default deserialization done by Gson. You will
* also need to register this deserializer through
* {@link GsonBuilder#registerTypeAdapter(Type, Object)}.</p>
@ -42,17 +42,19 @@ import java.lang.reflect.Type;
* </pre>
*
* <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the
* Json string to be <code>{"clazz":com.foo.MyObject,"value":20}</code>. Suppose, you already know
* JSON string to be <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you already know
* the 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>
*
* <pre>
* class IdDeserializer implements JsonDeserializer&lt;Id&gt;() {
* class IdDeserializer implements JsonDeserializer&lt;Id&gt; {
* public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
* throws JsonParseException {
* return new Id((Class)typeOfT, id.getValue());
* long idValue = json.getAsJsonPrimitive().getAsLong();
* return new Id((Class) typeOfT, idValue);
* }
* }
* </pre>
*
* <p>You will also need to register {@code IdDeserializer} with Gson as follows:</p>

View File

@ -24,22 +24,32 @@ import java.math.BigDecimal;
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 JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public abstract class JsonElement {
/**
* @deprecated Creating custom {@code JsonElement} subclasses is highly discouraged
* and can lead to undefined behavior.<br>
* This constructor is only kept for backward compatibility.
*/
@Deprecated
public JsonElement() {
}
/**
* Returns a deep copy of this element. Immutable elements like primitives
* and nulls are not copied.
*
* @since 2.8.2
*/
public abstract JsonElement deepCopy();
/**
* provides check for verifying if this element is an array or not.
* Provides a check for verifying if this element is a JSON array or not.
*
* @return true if this element is of type {@link JsonArray}, false otherwise.
*/
@ -48,7 +58,7 @@ public abstract class JsonElement {
}
/**
* provides check for verifying if this element is a Json object or not.
* Provides a check for verifying if this element is a JSON object or not.
*
* @return true if this element is of type {@link JsonObject}, false otherwise.
*/
@ -57,7 +67,7 @@ public abstract class JsonElement {
}
/**
* provides check for verifying if this element is a primitive or not.
* Provides a check for verifying if this element is a primitive or not.
*
* @return true if this element is of type {@link JsonPrimitive}, false otherwise.
*/
@ -66,7 +76,7 @@ public abstract class JsonElement {
}
/**
* provides check for verifying if this element represents a null value or not.
* Provides a check for verifying if this element represents a null value or not.
*
* @return true if this element is of type {@link JsonNull}, false otherwise.
* @since 1.2
@ -76,13 +86,13 @@ public abstract class JsonElement {
}
/**
* convenience method to get this element as a {@link JsonObject}. If the element is of some
* other type, a {@link IllegalStateException} will result. Hence it is best to use this method
* Convenience method to get this element as a {@link JsonObject}. If this element is of some
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonObject()}
* first.
*
* @return get this element as a {@link JsonObject}.
* @throws IllegalStateException if the element is of another type.
* @return this element as a {@link JsonObject}.
* @throws IllegalStateException if this element is of another type.
*/
public JsonObject getAsJsonObject() {
if (isJsonObject()) {
@ -92,13 +102,13 @@ public abstract class JsonElement {
}
/**
* convenience method to get this element as a {@link JsonArray}. If the element is of some
* other type, a {@link IllegalStateException} will result. Hence it is best to use this method
* Convenience method to get this element as a {@link JsonArray}. If this element is of some
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonArray()}
* first.
*
* @return get this element as a {@link JsonArray}.
* @throws IllegalStateException if the element is of another type.
* @return this element as a {@link JsonArray}.
* @throws IllegalStateException if this element is of another type.
*/
public JsonArray getAsJsonArray() {
if (isJsonArray()) {
@ -108,13 +118,13 @@ public abstract class JsonElement {
}
/**
* convenience method to get this element as a {@link JsonPrimitive}. If the element is of some
* other type, a {@link IllegalStateException} will result. Hence it is best to use this method
* Convenience method to get this element as a {@link JsonPrimitive}. If this element is of some
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonPrimitive()}
* first.
*
* @return get this element as a {@link JsonPrimitive}.
* @throws IllegalStateException if the element is of another type.
* @return this element as a {@link JsonPrimitive}.
* @throws IllegalStateException if this element is of another type.
*/
public JsonPrimitive getAsJsonPrimitive() {
if (isJsonPrimitive()) {
@ -124,13 +134,13 @@ public abstract class JsonElement {
}
/**
* convenience method to get this element as a {@link JsonNull}. If the element is of some
* other type, a {@link IllegalStateException} will result. Hence it is best to use this method
* Convenience method to get this element as a {@link JsonNull}. If this element is of some
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #isJsonNull()}
* first.
*
* @return get this element as a {@link JsonNull}.
* @throws IllegalStateException if the element is of another type.
* @return this element as a {@link JsonNull}.
* @throws IllegalStateException if this element is of another type.
* @since 1.2
*/
public JsonNull getAsJsonNull() {
@ -141,12 +151,11 @@ 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 get this element as a primitive boolean value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* boolean value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive boolean value.
* @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
* more than a single element.
*/
public boolean getAsBoolean() {
@ -154,12 +163,12 @@ 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 get this element as a {@link Number}.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* number.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a {@link Number}.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray},
* or cannot be converted to a number.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public Number getAsNumber() {
@ -167,12 +176,11 @@ 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 get this element as a string value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* string value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a string value.
* @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
* more than a single element.
*/
public String getAsString() {
@ -180,12 +188,12 @@ 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 get this element as a primitive double value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* double value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive double value.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public double getAsDouble() {
@ -193,12 +201,12 @@ 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 get this element as a primitive float value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* float value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive float value.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public float getAsFloat() {
@ -206,12 +214,12 @@ 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 get this element as a primitive long value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* long value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive long value.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public long getAsLong() {
@ -219,12 +227,12 @@ 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 get this element as a primitive integer value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* integer value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive integer value.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public int getAsInt() {
@ -232,12 +240,12 @@ 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 get this element as a primitive byte value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* byte value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive byte value.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.3
*/
@ -246,13 +254,12 @@ public abstract class JsonElement {
}
/**
* convenience method to get the first character of this element as a string or the first
* character of this array's first element as a string.
* Convenience method to get the first character of the string value of this element.
*
* @return the first character of the string.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* string value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return the first character of the string value.
* @throws UnsupportedOperationException if this element is not a {@link JsonPrimitive} or {@link JsonArray},
* or if its string value is empty.
* @throws IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.3
* @deprecated This method is misleading, as it does not get this element as a char but rather as
@ -264,12 +271,12 @@ 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 get this element as a {@link BigDecimal}.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive}.
* * @throws NumberFormatException if the element is not a valid {@link BigDecimal}.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a {@link BigDecimal}.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.2
*/
@ -278,12 +285,12 @@ 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 get this element as a {@link BigInteger}.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive}.
* @throws NumberFormatException if the element is not a valid {@link BigInteger}.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a {@link BigInteger}.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
* @since 1.2
*/
@ -292,12 +299,12 @@ 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 get this element as a primitive short value.
* @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
* short value.
* @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
* @return this element as a primitive short value.
* @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 IllegalStateException if this element is of the type {@link JsonArray} but contains
* more than a single element.
*/
public short getAsShort() {

View File

@ -17,7 +17,7 @@
package com.google.gson;
/**
* A class representing a Json {@code null} value.
* A class representing a JSON {@code null} value.
*
* @author Inderjeet Singh
* @author Joel Leitch
@ -25,15 +25,16 @@ package com.google.gson;
*/
public final class JsonNull extends JsonElement {
/**
* singleton for JsonNull
* Singleton for {@code JsonNull}.
*
* @since 1.8
*/
public static final JsonNull INSTANCE = new JsonNull();
/**
* Creates a new JsonNull object.
* Deprecated since Gson version 1.8. Use {@link #INSTANCE} instead
* Creates a new {@code JsonNull} object.
*
* @deprecated Deprecated since Gson version 1.8, use {@link #INSTANCE} instead.
*/
@Deprecated
public JsonNull() {
@ -41,7 +42,8 @@ public final class JsonNull extends JsonElement {
}
/**
* Returns the same instance since it is an immutable value
* Returns the same instance since it is an immutable value.
*
* @since 2.8.2
*/
@Override
@ -50,7 +52,7 @@ public final class JsonNull extends JsonElement {
}
/**
* All instances of 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() {
@ -58,7 +60,7 @@ public final class JsonNull extends JsonElement {
}
/**
* All instances of JsonNull are the same
* All instances of {@code JsonNull} are considered equal.
*/
@Override
public boolean equals(Object other) {

View File

@ -17,7 +17,6 @@
package com.google.gson;
import com.google.gson.internal.LinkedTreeMap;
import java.util.Map;
import java.util.Set;
@ -25,15 +24,25 @@ import java.util.Set;
* A class representing an object type in Json. An object consists of name-value pairs where names
* are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
* tree of JsonElements. The member elements of this object are maintained in order they were added.
* This class does not support {@code null} values. If {@code null} is provided as value argument
* to any of the methods, it is converted to a {@link JsonNull}.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
public final class JsonObject extends JsonElement {
private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>();
private final LinkedTreeMap<String, JsonElement> members = new LinkedTreeMap<>(false);
/**
* Creates a deep copy of this element and all its children
* Creates an empty JsonObject.
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonObject() {
}
/**
* Creates a deep copy of this element and all its children.
*
* @since 2.8.2
*/
@Override
@ -47,7 +56,7 @@ 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
* can be an arbitrary 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 JsonElements
* rooted at this node.
*
* @param property name of the member.
@ -58,10 +67,11 @@ public final class JsonObject extends JsonElement {
}
/**
* Removes the {@code property} from this {@link JsonObject}.
* Removes the {@code property} from this object.
*
* @param property name of the member that should be removed.
* @return the {@link JsonElement} object that is being removed.
* @return the {@link JsonElement} object that is being removed, or {@code null} if no
* member with this name exists.
* @since 1.3
*/
public JsonElement remove(String property) {
@ -69,8 +79,8 @@ public final class JsonObject extends JsonElement {
}
/**
* Convenience method to add a primitive member. The specified value is converted to a
* JsonPrimitive of String.
* Convenience method to add a string member. The specified value is converted to a
* {@link JsonPrimitive} of String.
*
* @param property name of the member.
* @param value the string value associated with the member.
@ -80,8 +90,8 @@ public final class JsonObject extends JsonElement {
}
/**
* Convenience method to add a primitive member. The specified value is converted to a
* JsonPrimitive of Number.
* Convenience method to add a number member. The specified value is converted to a
* {@link JsonPrimitive} of Number.
*
* @param property name of the member.
* @param value the number value associated with the member.
@ -92,10 +102,10 @@ public final class JsonObject extends JsonElement {
/**
* Convenience method to add a boolean member. The specified value is converted to a
* JsonPrimitive of Boolean.
* {@link JsonPrimitive} of Boolean.
*
* @param property name of the member.
* @param value the number value associated with the member.
* @param value the boolean value associated with the member.
*/
public void addProperty(String property, Boolean value) {
add(property, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value));
@ -103,10 +113,10 @@ public final class JsonObject extends JsonElement {
/**
* Convenience method to add a char member. The specified value is converted to a
* JsonPrimitive of Character.
* {@link JsonPrimitive} of Character.
*
* @param property name of the member.
* @param value the number value associated with the member.
* @param value the char value associated with the member.
*/
public void addProperty(String property, Character value) {
add(property, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value));
@ -155,48 +165,63 @@ public final class JsonObject extends JsonElement {
* Returns the member with the specified name.
*
* @param memberName name of the member that is being requested.
* @return the member matching the name. Null if no such member exists.
* @return the member matching the name, or {@code null} if no such member exists.
*/
public JsonElement get(String memberName) {
return members.get(memberName);
}
/**
* Convenience method to get the specified member as a JsonPrimitive element.
* Convenience method to get the specified member as a {@link JsonPrimitive}.
*
* @param memberName name of the member being requested.
* @return the JsonPrimitive corresponding to the specified member.
* @return the {@code JsonPrimitive} corresponding to the specified member, or {@code null} if no
* member with this name exists.
* @throws ClassCastException if the member is not of type {@code JsonPrimitive}.
*/
public JsonPrimitive getAsJsonPrimitive(String memberName) {
return (JsonPrimitive) members.get(memberName);
}
/**
* Convenience method to get the specified member as a JsonArray.
* Convenience method to get the specified member as a {@link JsonArray}.
*
* @param memberName name of the member being requested.
* @return the JsonArray corresponding to the specified member.
* @return the {@code JsonArray} corresponding to the specified member, or {@code null} if no
* member with this name exists.
* @throws ClassCastException if the member is not of type {@code JsonArray}.
*/
public JsonArray getAsJsonArray(String memberName) {
return (JsonArray) members.get(memberName);
}
/**
* Convenience method to get the specified member as a JsonObject.
* Convenience method to get the specified member as a {@link JsonObject}.
*
* @param memberName name of the member being requested.
* @return the JsonObject corresponding to the specified member.
* @return the {@code JsonObject} corresponding to the specified member, or {@code null} if no
* member with this name exists.
* @throws ClassCastException if the member is not of type {@code JsonObject}.
*/
public JsonObject getAsJsonObject(String memberName) {
return (JsonObject) members.get(memberName);
}
/**
* Returns whether the other object is equal to this. This method only considers
* the other object to be equal if it is an instance of {@code JsonObject} and has
* equal members, ignoring order.
*/
@Override
public boolean equals(Object o) {
return (o == this) || (o instanceof JsonObject
&& ((JsonObject) o).members.equals(members));
}
/**
* Returns the hash code of this object. This method calculates the hash code based
* on the members of this object, ignoring order.
*/
@Override
public int hashCode() {
return members.hashCode();

View File

@ -15,17 +15,16 @@
*/
package com.google.gson;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.io.Reader;
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 Joel Leitch
@ -37,7 +36,11 @@ public final class JsonParser {
public JsonParser() {}
/**
* Parses the specified JSON string into a parse tree
* 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#setLenient(boolean) lenient mode}.
*
* @param json JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
@ -48,11 +51,16 @@ public final class JsonParser {
}
/**
* Parses the specified JSON string into a parse tree
* 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,
* or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode}.
*
* @param reader JSON text
* @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
* @throws JsonParseException if the specified text is not valid JSON
* @throws JsonParseException if there is an IOException or if the specified
* text is not valid JSON
*/
public static JsonElement parseReader(Reader reader) throws JsonIOException, JsonSyntaxException {
try {
@ -73,6 +81,12 @@ public final class JsonParser {
/**
* Returns the next value from the JSON stream as a parse tree.
* Unlike the other {@code parse} methods, no exception is thrown if the JSON data has
* multiple top-level JSON elements, or if there is trailing data.
*
* <p>The JSON data is parsed in {@linkplain JsonReader#setLenient(boolean) lenient mode},
* regardless of the lenient mode setting of the provided reader. The lenient mode setting
* of the reader is restored once this method returns.
*
* @throws JsonParseException if there is an IOException or if the specified
* text is not valid JSON

View File

@ -16,14 +16,13 @@
package com.google.gson;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.LazilyParsedNumber;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.google.gson.internal.LazilyParsedNumber;
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 primitive, or a Java primitive
* wrapper type.
*
@ -39,8 +38,9 @@ public final class JsonPrimitive extends JsonElement {
*
* @param bool the value to create the primitive with.
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonPrimitive(Boolean bool) {
value = $Gson$Preconditions.checkNotNull(bool);
value = Objects.requireNonNull(bool);
}
/**
@ -48,8 +48,9 @@ public final class JsonPrimitive extends JsonElement {
*
* @param number the value to create the primitive with.
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonPrimitive(Number number) {
value = $Gson$Preconditions.checkNotNull(number);
value = Objects.requireNonNull(number);
}
/**
@ -57,24 +58,27 @@ public final class JsonPrimitive extends JsonElement {
*
* @param string the value to create the primitive with.
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonPrimitive(String string) {
value = $Gson$Preconditions.checkNotNull(string);
value = Objects.requireNonNull(string);
}
/**
* Create a primitive containing a character. The character is turned into a one character String
* since Json only supports String.
* since JSON only supports String.
*
* @param c the value to create the primitive with.
*/
@SuppressWarnings("deprecation") // superclass constructor
public JsonPrimitive(Character c) {
// convert characters to strings since in JSON, characters are represented as a single
// character string
value = $Gson$Preconditions.checkNotNull(c).toString();
value = Objects.requireNonNull(c).toString();
}
/**
* Returns the same value as primitives are immutable.
*
* @since 2.8.2
*/
@Override
@ -92,9 +96,10 @@ public final class JsonPrimitive extends JsonElement {
}
/**
* convenience method to get this element as a boolean value.
*
* @return get this element as a primitive boolean value.
* Convenience method to get this element as a boolean value.
* If this primitive {@linkplain #isBoolean() is not a boolean}, the string value
* is parsed using {@link Boolean#parseBoolean(String)}. This means {@code "true"} (ignoring
* case) is considered {@code true} and any other value is considered {@code false}.
*/
@Override
public boolean getAsBoolean() {
@ -115,14 +120,21 @@ public final class JsonPrimitive extends JsonElement {
}
/**
* convenience method to get this element as a Number.
* Convenience method to get this element as a {@link Number}.
* If this primitive {@linkplain #isString() is a string}, a lazily parsed {@code Number}
* is constructed which parses the string when any of its methods are called (which can
* lead to a {@link NumberFormatException}).
*
* @return get this element as a Number.
* @throws NumberFormatException if the value contained is not a valid Number.
* @throws UnsupportedOperationException if this primitive is neither a number nor a string.
*/
@Override
public Number getAsNumber() {
return value instanceof String ? new LazilyParsedNumber((String) value) : (Number) value;
if (value instanceof Number) {
return (Number) value;
} else if (value instanceof String) {
return new LazilyParsedNumber((String) value);
}
throw new UnsupportedOperationException("Primitive is neither a number nor a string");
}
/**
@ -134,27 +146,21 @@ public final class JsonPrimitive extends JsonElement {
return value instanceof String;
}
/**
* convenience method to get this element as a String.
*
* @return get this element as a String.
*/
// Don't add Javadoc, inherit it from super implementation; no exceptions are thrown here
@Override
public String getAsString() {
if (isNumber()) {
if (value instanceof String) {
return (String) value;
} else if (isNumber()) {
return getAsNumber().toString();
} else if (isBoolean()) {
return ((Boolean) value).toString();
} else {
return (String) value;
}
throw new AssertionError("Unexpected value type: " + value.getClass());
}
/**
* convenience method to get this element as a primitive double.
*
* @return get this element as a primitive double.
* @throws NumberFormatException if the value contained is not a valid double.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public double getAsDouble() {
@ -162,33 +168,24 @@ public final class JsonPrimitive extends JsonElement {
}
/**
* convenience method to get this element as a {@link BigDecimal}.
*
* @return get this element as a {@link BigDecimal}.
* @throws NumberFormatException if the value contained is not a valid {@link BigDecimal}.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public BigDecimal getAsBigDecimal() {
return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString());
return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(getAsString());
}
/**
* convenience method to get this element as a {@link BigInteger}.
*
* @return get this element as a {@link BigInteger}.
* @throws NumberFormatException if the value contained is not a valid {@link BigInteger}.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public BigInteger getAsBigInteger() {
return value instanceof BigInteger ?
(BigInteger) value : new BigInteger(value.toString());
(BigInteger) value : new BigInteger(getAsString());
}
/**
* convenience method to get this element as a float.
*
* @return get this element as a float.
* @throws NumberFormatException if the value contained is not a valid float.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public float getAsFloat() {
@ -196,10 +193,10 @@ public final class JsonPrimitive extends JsonElement {
}
/**
* convenience method to get this element as a primitive long.
* Convenience method to get this element as a primitive long.
*
* @return get this element as a primitive long.
* @throws NumberFormatException if the value contained is not a valid long.
* @return this element as a primitive long.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public long getAsLong() {
@ -207,10 +204,7 @@ public final class JsonPrimitive extends JsonElement {
}
/**
* convenience method to get this element as a primitive short.
*
* @return get this element as a primitive short.
* @throws NumberFormatException if the value contained is not a valid short value.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public short getAsShort() {
@ -218,26 +212,41 @@ public final class JsonPrimitive extends JsonElement {
}
/**
* convenience method to get this element as a primitive integer.
*
* @return get this element as a primitive integer.
* @throws NumberFormatException if the value contained is not a valid integer.
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public int getAsInt() {
return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString());
}
/**
* @throws NumberFormatException {@inheritDoc}
*/
@Override
public byte getAsByte() {
return isNumber() ? getAsNumber().byteValue() : Byte.parseByte(getAsString());
}
/**
* @throws UnsupportedOperationException if the string value of this
* primitive is empty.
* @deprecated This method is misleading, as it does not get this element as a char but rather as
* a string's first character.
*/
@Deprecated
@Override
public char getAsCharacter() {
return getAsString().charAt(0);
String s = getAsString();
if (s.isEmpty()) {
throw new UnsupportedOperationException("String value is empty");
} else {
return s.charAt(0);
}
}
/**
* Returns the hash code of this object.
*/
@Override
public int hashCode() {
if (value == null) {
@ -255,6 +264,11 @@ public final class JsonPrimitive extends JsonElement {
return value.hashCode();
}
/**
* Returns whether the other object is equal to this. This method only considers
* the other object to be equal if it is an instance of {@code JsonPrimitive} and
* has an equal value.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {

View File

@ -19,7 +19,7 @@ package com.google.gson;
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 are not happy with the default serialization done by Gson. You will also need to register
* this serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}.
*
@ -43,12 +43,12 @@ import java.lang.reflect.Type;
* </pre>
*
* <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be
* <code>{"clazz":com.foo.MyObject,"value":20}</code>. Suppose, you just want the output to be
* <code>{"clazz":"com.foo.MyObject","value":20}</code>. Suppose, you just want the output to be
* the value instead, which is {@code 20} in this case. You can achieve that by writing a custom
* serializer:</p>
*
* <pre>
* class IdSerializer implements JsonSerializer&lt;Id&gt;() {
* class IdSerializer implements JsonSerializer&lt;Id&gt; {
* public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
* return new JsonPrimitive(id.getValue());
* }

View File

@ -16,8 +16,8 @@
package com.google.gson;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.internal.bind.JsonTreeReader;
import com.google.gson.internal.bind.JsonTreeWriter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
@ -131,8 +131,7 @@ public abstract class TypeAdapter<T> {
* Unlike Gson's similar {@link Gson#toJson(JsonElement, Appendable) toJson}
* method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(com.google.gson.stream.JsonWriter, Object)} for lenient
* writing.
* {@link #write(JsonWriter, Object)} for lenient writing.
*
* @param value the Java object to convert. May be null.
* @since 2.2
@ -205,9 +204,9 @@ public abstract class TypeAdapter<T> {
* Converts {@code value} to a JSON document. Unlike Gson's similar {@link
* Gson#toJson(Object) toJson} method, this write is strict. Create a {@link
* JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
* {@link #write(com.google.gson.stream.JsonWriter, Object)} for lenient
* writing.
* {@link #write(JsonWriter, Object)} for lenient writing.
*
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
* @param value the Java object to convert. May be null.
* @since 2.2
*/
@ -216,7 +215,7 @@ public abstract class TypeAdapter<T> {
try {
toJson(stringWriter, value);
} catch (IOException e) {
throw new AssertionError(e); // No I/O writing to a StringWriter.
throw new JsonIOException(e);
}
return stringWriter.toString();
}
@ -226,6 +225,7 @@ public abstract class TypeAdapter<T> {
*
* @param value the Java object to convert. May be null.
* @return the converted JSON tree. May be {@link JsonNull}.
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #write(JsonWriter, Object)}
* @since 2.2
*/
public final JsonElement toJsonTree(T value) {
@ -248,10 +248,13 @@ public abstract class TypeAdapter<T> {
/**
* Converts the JSON document in {@code in} to a Java object. Unlike Gson's
* similar {@link Gson#fromJson(java.io.Reader, Class) fromJson} method, this
* similar {@link Gson#fromJson(Reader, Class) fromJson} method, this
* read is strict. Create a {@link JsonReader#setLenient(boolean) lenient}
* {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @return the converted Java object. May be null.
* @since 2.2
*/
@ -266,6 +269,9 @@ public abstract class TypeAdapter<T> {
* strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code
* JsonReader} and call {@link #read(JsonReader)} for lenient reading.
*
* <p>No exception is thrown if the JSON data has multiple top-level JSON elements,
* or if there is trailing data.
*
* @return the converted Java object. May be null.
* @since 2.2
*/
@ -276,7 +282,9 @@ public abstract class TypeAdapter<T> {
/**
* Converts {@code jsonTree} to a Java object.
*
* @param jsonTree the Java object to convert. May be {@link JsonNull}.
* @param jsonTree the JSON element to convert. May be {@link JsonNull}.
* @return the converted Java object. May be null.
* @throws JsonIOException wrapping {@code IOException}s thrown by {@link #read(JsonReader)}
* @since 2.2
*/
public final T fromJsonTree(JsonElement jsonTree) {

View File

@ -16,6 +16,8 @@
package com.google.gson.internal;
import java.util.Objects;
/**
* A simple utility class used to check method Preconditions.
*
@ -34,6 +36,12 @@ public final class $Gson$Preconditions {
throw new UnsupportedOperationException();
}
/**
* @deprecated
* This is an internal Gson method. Use {@link Objects#requireNonNull(Object)} instead.
*/
// Only deprecated for now because external projects might be using this by accident
@Deprecated
public static <T> T checkNotNull(T obj) {
if (obj == null) {
throw new NullPointerException();

View File

@ -16,6 +16,9 @@
package com.google.gson.internal;
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
@ -32,9 +35,6 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
/**
* Static methods for working with types.
*
@ -486,6 +486,7 @@ public final class $Gson$Types {
private final Type[] typeArguments;
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
requireNonNull(rawType);
// require an owner type if the raw type needs it
if (rawType instanceof Class<?>) {
Class<?> rawTypeAsClass = (Class<?>) rawType;
@ -498,7 +499,7 @@ public final class $Gson$Types {
this.rawType = canonicalize(rawType);
this.typeArguments = typeArguments.clone();
for (int t = 0, length = this.typeArguments.length; t < length; t++) {
checkNotNull(this.typeArguments[t]);
requireNonNull(this.typeArguments[t]);
checkNotPrimitive(this.typeArguments[t]);
this.typeArguments[t] = canonicalize(this.typeArguments[t]);
}
@ -552,6 +553,7 @@ public final class $Gson$Types {
private final Type componentType;
public GenericArrayTypeImpl(Type componentType) {
requireNonNull(componentType);
this.componentType = canonicalize(componentType);
}
@ -590,14 +592,14 @@ public final class $Gson$Types {
checkArgument(upperBounds.length == 1);
if (lowerBounds.length == 1) {
checkNotNull(lowerBounds[0]);
requireNonNull(lowerBounds[0]);
checkNotPrimitive(lowerBounds[0]);
checkArgument(upperBounds[0] == Object.class);
this.lowerBound = canonicalize(lowerBounds[0]);
this.upperBound = Object.class;
} else {
checkNotNull(upperBounds[0]);
requireNonNull(upperBounds[0]);
checkNotPrimitive(upperBounds[0]);
this.lowerBound = null;
this.upperBound = canonicalize(upperBounds[0]);

View File

@ -26,6 +26,7 @@ import java.math.BigDecimal;
*
* @author Inderjeet Singh
*/
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID
public final class LazilyParsedNumber extends Number {
private final String value;

View File

@ -38,6 +38,7 @@ import java.util.Set;
*
* <p>This implementation was derived from Android 4.1's TreeMap class.
*/
@SuppressWarnings("serial") // ignore warning about missing serialVersionUID
public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
@SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>>
private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
@ -46,21 +47,33 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
}
};
Comparator<? super K> comparator;
private final Comparator<? super K> comparator;
private final boolean allowNullValues;
Node<K, V> root;
int size = 0;
int modCount = 0;
// Used to preserve iteration order
final Node<K, V> header = new Node<>();
final Node<K, V> header;
/**
* Create a natural order, empty tree map whose keys must be mutually
* comparable and non-null, and whose values can be {@code null}.
*/
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
public LinkedTreeMap() {
this((Comparator<? super K>) NATURAL_ORDER, true);
}
/**
* Create a natural order, empty tree map whose keys must be mutually
* comparable and non-null.
*
* @param allowNullValues whether {@code null} is allowed as entry value
*/
@SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
public LinkedTreeMap() {
this((Comparator<? super K>) NATURAL_ORDER);
public LinkedTreeMap(boolean allowNullValues) {
this((Comparator<? super K>) NATURAL_ORDER, allowNullValues);
}
/**
@ -69,12 +82,15 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
*
* @param comparator the comparator to order elements with, or {@code null} to
* use the natural ordering.
* @param allowNullValues whether {@code null} is allowed as entry value
*/
@SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable
public LinkedTreeMap(Comparator<? super K> comparator) {
public LinkedTreeMap(Comparator<? super K> comparator, boolean allowNullValues) {
this.comparator = comparator != null
? comparator
: (Comparator) NATURAL_ORDER;
this.allowNullValues = allowNullValues;
this.header = new Node<>(allowNullValues);
}
@Override public int size() {
@ -94,6 +110,9 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
if (key == null) {
throw new NullPointerException("key == null");
}
if (value == null && !allowNullValues) {
throw new NullPointerException("value == null");
}
Node<K, V> created = find(key, true);
V result = created.value;
created.value = value;
@ -166,10 +185,10 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
throw new ClassCastException(key.getClass().getName() + " is not Comparable");
}
created = new Node<>(nearest, key, header, header.prev);
created = new Node<>(allowNullValues, nearest, key, header, header.prev);
root = created;
} else {
created = new Node<>(nearest, key, header, header.prev);
created = new Node<>(allowNullValues, nearest, key, header, header.prev);
if (comparison < 0) { // nearest.key is higher
nearest.left = created;
} else { // comparison > 0, nearest.key is lower
@ -446,19 +465,22 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
Node<K, V> next;
Node<K, V> prev;
final K key;
final boolean allowNullValue;
V value;
int height;
/** Create the header entry */
Node() {
Node(boolean allowNullValue) {
key = null;
this.allowNullValue = allowNullValue;
next = prev = this;
}
/** Create a regular entry */
Node(Node<K, V> parent, K key, Node<K, V> next, Node<K, V> prev) {
Node(boolean allowNullValue, Node<K, V> parent, K key, Node<K, V> next, Node<K, V> prev) {
this.parent = parent;
this.key = key;
this.allowNullValue = allowNullValue;
this.height = 1;
this.next = next;
this.prev = prev;
@ -475,15 +497,17 @@ public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Seri
}
@Override public V setValue(V value) {
if (value == null && !allowNullValue) {
throw new NullPointerException("value == null");
}
V oldValue = this.value;
this.value = value;
return oldValue;
}
@SuppressWarnings("rawtypes")
@Override public boolean equals(Object o) {
if (o instanceof Entry) {
Entry other = (Entry) o;
Entry<?, ?> other = (Entry<?, ?>) o;
return (key == null ? other.getKey() == null : key.equals(other.getKey()))
&& (value == null ? other.getValue() == null : value.equals(other.getValue()));
}

View File

@ -28,6 +28,7 @@ import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
/**
* Reads and writes GSON parse trees over streams.
@ -89,22 +90,48 @@ public final class Streams {
}
@Override public void write(char[] chars, int offset, int length) throws IOException {
currentWrite.chars = chars;
currentWrite.setChars(chars);
appendable.append(currentWrite, offset, offset + length);
}
@Override public void write(int i) throws IOException {
appendable.append((char) i);
}
@Override public void flush() {}
@Override public void close() {}
// Override these methods for better performance
// They would otherwise unnecessarily create Strings or char arrays
@Override public void write(int i) throws IOException {
appendable.append((char) i);
}
@Override public void write(String str, int off, int len) throws IOException {
// Appendable.append turns null -> "null", which is not desired here
Objects.requireNonNull(str);
appendable.append(str, off, off + len);
}
@Override public Writer append(CharSequence csq) throws IOException {
appendable.append(csq);
return this;
}
@Override public Writer append(CharSequence csq, int start, int end) throws IOException {
appendable.append(csq, start, end);
return this;
}
/**
* A mutable char sequence pointing at a single char[].
*/
static class CurrentWrite implements CharSequence {
char[] chars;
private static class CurrentWrite implements CharSequence {
private char[] chars;
private String cachedString;
void setChars(char[] chars) {
this.chars = chars;
this.cachedString = null;
}
@Override public int length() {
return chars.length;
}
@ -114,7 +141,14 @@ public final class Streams {
@Override public CharSequence subSequence(int start, int end) {
return new String(chars, start, end - start);
}
// Must return string representation to satisfy toString() contract
@Override public String toString() {
if (cachedString == null) {
cachedString = new String(chars);
}
return cachedString;
}
}
}
}

View File

@ -16,13 +16,6 @@
package com.google.gson.internal.bind;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
@ -31,13 +24,17 @@ 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.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.ArrayList;
/**
* Adapt an array of objects.
*/
public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (!(type instanceof GenericArrayType || type instanceof Class && ((Class<?>) type).isArray())) {
@ -46,8 +43,11 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
Type componentType = $Gson$Types.getArrayComponentType(type);
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
return new ArrayTypeAdapter(
@SuppressWarnings({"unchecked", "rawtypes"})
TypeAdapter<T> arrayAdapter = new ArrayTypeAdapter(
gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
return arrayAdapter;
}
};
@ -65,15 +65,14 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
in.nextNull();
return null;
}
Object array;
if (in.isLenient() && in.peek() != JsonToken.BEGIN_ARRAY) {
// Coerce
array = Array.newInstance(componentType, 1);
Object array = Array.newInstance(componentType, 1);
Array.set(array, 0, componentTypeAdapter.read(in));
return array;
}
List<E> list = new ArrayList<>();
ArrayList<E> list = new ArrayList<>();
in.beginArray();
while (in.hasNext()) {
E instance = componentTypeAdapter.read(in);
@ -82,14 +81,22 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
in.endArray();
int size = list.size();
array = Array.newInstance(componentType, size);
for (int i = 0; i < size; i++) {
Array.set(array, i, list.get(i));
// Have to copy primitives one by one to primitive array
if (componentType.isPrimitive()) {
Object array = Array.newInstance(componentType, size);
for (int i = 0; i < size; i++) {
Array.set(array, i, list.get(i));
}
return array;
}
// But for Object[] can use ArrayList.toArray
else {
@SuppressWarnings("unchecked")
E[] array = (E[]) Array.newInstance(componentType, size);
return list.toArray(array);
}
return array;
}
@SuppressWarnings("unchecked")
@Override public void write(JsonWriter out, Object array) throws IOException {
if (array == null) {
out.nullValue();
@ -98,6 +105,7 @@ public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
out.beginArray();
for (int i = 0, length = Array.getLength(array); i < length; i++) {
@SuppressWarnings("unchecked")
E value = (E) Array.get(array, i);
componentTypeAdapter.write(out, value);
}

View File

@ -16,6 +16,15 @@
package com.google.gson.internal.bind;
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.internal.bind.util.ISO8601Utils;
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;
@ -25,17 +34,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.JavaVersion;
import com.google.gson.internal.PreJava9DateFormatProvider;
import com.google.gson.internal.bind.util.ISO8601Utils;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.util.Objects;
/**
* This type adapter supports subclasses of date by defining a
@ -93,7 +92,7 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
private final List<DateFormat> dateFormats = new ArrayList<>();
private DefaultDateTypeAdapter(DateType<T> dateType, String datePattern) {
this.dateType = $Gson$Preconditions.checkNotNull(dateType);
this.dateType = Objects.requireNonNull(dateType);
dateFormats.add(new SimpleDateFormat(datePattern, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(new SimpleDateFormat(datePattern));
@ -101,7 +100,7 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
}
private DefaultDateTypeAdapter(DateType<T> dateType, int style) {
this.dateType = $Gson$Preconditions.checkNotNull(dateType);
this.dateType = Objects.requireNonNull(dateType);
dateFormats.add(DateFormat.getDateInstance(style, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateInstance(style));
@ -112,7 +111,7 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
}
private DefaultDateTypeAdapter(DateType<T> dateType, int dateStyle, int timeStyle) {
this.dateType = $Gson$Preconditions.checkNotNull(dateType);
this.dateType = Objects.requireNonNull(dateType);
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle));

View File

@ -38,7 +38,7 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
this.constructorConstructor = constructorConstructor;
}
@SuppressWarnings("unchecked")
@SuppressWarnings("unchecked") // this is not safe; requires that user has specified correct adapter class for @JsonAdapter
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
Class<? super T> rawType = targetType.getRawType();
@ -49,24 +49,29 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
}
@SuppressWarnings({ "unchecked", "rawtypes" }) // Casts guarded by conditionals.
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
TypeToken<?> type, JsonAdapter annotation) {
Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct();
TypeAdapter<?> typeAdapter;
boolean nullSafe = annotation.nullSafe();
if (instance instanceof TypeAdapter) {
typeAdapter = (TypeAdapter<?>) instance;
} else if (instance instanceof TypeAdapterFactory) {
typeAdapter = ((TypeAdapterFactory) instance).create(gson, type);
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
JsonSerializer<?> serializer = instance instanceof JsonSerializer
? (JsonSerializer) instance
? (JsonSerializer<?>) instance
: null;
JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer
? (JsonDeserializer) instance
? (JsonDeserializer<?>) instance
: null;
typeAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null);
@SuppressWarnings({ "unchecked", "rawtypes" })
TypeAdapter<?> tempAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null, nullSafe);
typeAdapter = tempAdapter;
nullSafe = false;
} else {
throw new IllegalArgumentException("Invalid attempt to bind an instance of "
+ instance.getClass().getName() + " as a @JsonAdapter for " + type.toString()
@ -74,7 +79,7 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
+ " JsonSerializer or JsonDeserializer.");
}
if (typeAdapter != null && annotation.nullSafe()) {
if (typeAdapter != null && nullSafe) {
typeAdapter = typeAdapter.nullSafe();
}

View File

@ -23,11 +23,12 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Arrays;
/**
* This reader walks the elements of a JsonElement as if it was coming from a
@ -143,7 +144,7 @@ public final class JsonTreeReader extends JsonReader {
} else if (o == SENTINEL_CLOSED) {
throw new IllegalStateException("JsonReader is closed");
} else {
throw new AssertionError();
throw new MalformedJsonException("Custom JsonElement subclass " + o.getClass().getName() + " is not supported");
}
}
@ -212,7 +213,7 @@ public final class JsonTreeReader extends JsonReader {
}
double result = ((JsonPrimitive) peekStack()).getAsDouble();
if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new NumberFormatException("JSON forbids NaN and infinities: " + result);
throw new MalformedJsonException("JSON forbids NaN and infinities: " + result);
}
popStack();
if (stackSize > 0) {

View File

@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This writer creates a JsonElement.
@ -72,7 +73,12 @@ public final class JsonTreeWriter extends JsonWriter {
return stack.get(stack.size() - 1);
}
private void put(JsonElement value) {
@Override
public JsonWriter comment(String comment) throws IOException {
return this;
}
private void put(JsonElement value) {
if (pendingName != null) {
if (!value.isJsonNull() || getSerializeNulls()) {
JsonObject object = (JsonObject) peek();
@ -130,9 +136,7 @@ public final class JsonTreeWriter extends JsonWriter {
}
@Override public JsonWriter name(String name) throws IOException {
if (name == null) {
throw new NullPointerException("name == null");
}
Objects.requireNonNull(name, "name == null");
if (stack.isEmpty() || pendingName != null) {
throw new IllegalStateException();
}
@ -152,6 +156,10 @@ public final class JsonTreeWriter extends JsonWriter {
return this;
}
@Override public JsonWriter jsonValue(String value) throws IOException {
throw new UnsupportedOperationException();
}
@Override public JsonWriter nullValue() throws IOException {
put(JsonNull.INSTANCE);
return this;
@ -170,6 +178,14 @@ public final class JsonTreeWriter extends JsonWriter {
return this;
}
@Override public JsonWriter value(float value) throws IOException {
if (!isLenient() && (Float.isNaN(value) || Float.isInfinite(value))) {
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
}
put(new JsonPrimitive(value));
return this;
}
@Override public JsonWriter value(double value) throws IOException {
if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);

View File

@ -17,8 +17,8 @@
package com.google.gson.internal.bind;
import com.google.gson.Gson;
import com.google.gson.ToNumberStrategy;
import com.google.gson.ToNumberPolicy;
import com.google.gson.ToNumberStrategy;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.LinkedTreeMap;
@ -26,9 +26,10 @@ 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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
@ -70,52 +71,108 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
}
}
@Override public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
List<Object> list = new ArrayList<>();
in.beginArray();
while (in.hasNext()) {
list.add(read(in));
}
in.endArray();
return list;
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<>();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map;
case STRING:
return in.nextString();
case NUMBER:
return toNumberStrategy.readNumber(in);
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
throw new IllegalStateException();
/**
* Tries to begin reading a JSON array or JSON object, returning {@code null} if
* the next element is neither of those.
*/
private Object tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case BEGIN_ARRAY:
in.beginArray();
return new ArrayList<>();
case BEGIN_OBJECT:
in.beginObject();
return new LinkedTreeMap<>();
default:
return null;
}
}
/** Reads an {@code Object} which cannot have any nested elements */
private Object readTerminal(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case STRING:
return in.nextString();
case NUMBER:
return toNumberStrategy.readNumber(in);
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
// When read(JsonReader) is called with JsonReader in invalid state
throw new IllegalStateException("Unexpected token: " + peeked);
}
}
@Override public Object read(JsonReader in) throws IOException {
// Either List or Map
Object current;
JsonToken peeked = in.peek();
current = tryBeginNesting(in, peeked);
if (current == null) {
return readTerminal(in, peeked);
}
Deque<Object> stack = new ArrayDeque<>();
while (true) {
while (in.hasNext()) {
String name = null;
// Name is only used for JSON object members
if (current instanceof Map) {
name = in.nextName();
}
peeked = in.peek();
Object value = tryBeginNesting(in, peeked);
boolean isNesting = value != null;
if (value == null) {
value = readTerminal(in, peeked);
}
if (current instanceof List) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) current;
list.add(value);
} else {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) current;
map.put(name, value);
}
if (isNesting) {
stack.addLast(current);
current = value;
}
}
// End current element
if (current instanceof List) {
in.endArray();
} else {
in.endObject();
}
if (stack.isEmpty()) {
return current;
} else {
// Continue with enclosing element
current = stack.removeLast();
}
}
}
@SuppressWarnings("unchecked")
@Override public void write(JsonWriter out, Object value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
@SuppressWarnings("unchecked")
TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
if (typeAdapter instanceof ObjectTypeAdapter) {
out.beginObject();

View File

@ -68,11 +68,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
this.reflectionFilters = reflectionFilters;
}
public boolean excludeField(Field f, boolean serialize) {
return excludeField(f, serialize, excluder);
}
static boolean excludeField(Field f, boolean serialize, Excluder excluder) {
private boolean includeField(Field f, boolean serialize) {
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
}
@ -130,19 +126,19 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
final TypeToken<?> fieldType, boolean serialize, boolean deserialize,
final boolean blockInaccessible) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
TypeAdapter<?> mapped = null;
if (annotation != null) {
// This is not safe; requires that user has specified correct adapter class for @JsonAdapter
mapped = jsonAdapterFactory.getTypeAdapter(
constructorConstructor, context, fieldType, annotation);
}
final boolean jsonAdapterPresent = mapped != null;
if (mapped == null) mapped = context.getAdapter(fieldType);
final TypeAdapter<?> typeAdapter = mapped;
@SuppressWarnings("unchecked")
final TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) mapped;
return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
@SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
@Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
if (!serialized) return;
@ -156,8 +152,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
return;
}
writer.name(name);
TypeAdapter t = jsonAdapterPresent ? typeAdapter
: new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
TypeAdapter<Object> t = jsonAdapterPresent ? typeAdapter
: new TypeAdapterRuntimeTypeWrapper<>(context, typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
@Override void read(JsonReader reader, Object value)
@ -196,8 +192,8 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
}
for (Field field : fields) {
boolean serialize = excludeField(field, true);
boolean deserialize = excludeField(field, false);
boolean serialize = includeField(field, true);
boolean deserialize = includeField(field, false);
if (!serialize && !deserialize) {
continue;
}

View File

@ -45,17 +45,24 @@ public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
private final TypeToken<T> typeToken;
private final TypeAdapterFactory skipPast;
private final GsonContextImpl context = new GsonContextImpl();
private final boolean nullSafe;
/** The delegate is lazily created because it may not be needed, and creating it may fail. */
private volatile TypeAdapter<T> delegate;
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) {
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast, boolean nullSafe) {
this.serializer = serializer;
this.deserializer = deserializer;
this.gson = gson;
this.typeToken = typeToken;
this.skipPast = skipPast;
this.nullSafe = nullSafe;
}
public TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) {
this(serializer, deserializer, gson, typeToken, skipPast, true);
}
@Override public T read(JsonReader in) throws IOException {
@ -63,7 +70,7 @@ public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
return delegate().read(in);
}
JsonElement value = Streams.parse(in);
if (value.isJsonNull()) {
if (nullSafe && value.isJsonNull()) {
return null;
}
return deserializer.deserialize(value, typeToken.getType(), context);
@ -74,7 +81,7 @@ public final class TreeTypeAdapter<T> extends TypeAdapter<T> {
delegate().write(out, value);
return;
}
if (value == null) {
if (nullSafe && value == null) {
out.nullValue();
return;
}

View File

@ -15,15 +15,14 @@
*/
package com.google.gson.internal.bind;
import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private final Gson context;
@ -41,7 +40,6 @@ final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
return delegate.read(in);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, T value) throws IOException {
// Order of preference for choosing type adapters
@ -50,10 +48,11 @@ final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
// Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
// Fourth preference: reflective type adapter for the declared type
TypeAdapter chosen = delegate;
TypeAdapter<T> chosen = delegate;
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) {
TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
@SuppressWarnings("unchecked")
TypeAdapter<T> runtimeTypeAdapter = (TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;

View File

@ -16,32 +16,6 @@
package com.google.gson.internal.bind;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Currency;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@ -58,6 +32,33 @@ 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.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Currency;
import java.util.Deque;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* Type adapters for basic types.
@ -695,44 +696,99 @@ public final class TypeAdapters {
public static final TypeAdapterFactory LOCALE_FACTORY = newFactory(Locale.class, LOCALE);
public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
/**
* Tries to begin reading a JSON array or JSON object, returning {@code null} if
* the next element is neither of those.
*/
private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case BEGIN_ARRAY:
in.beginArray();
return new JsonArray();
case BEGIN_OBJECT:
in.beginObject();
return new JsonObject();
default:
return null;
}
}
/** Reads a {@link JsonElement} which cannot have any nested elements */
private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException {
switch (peeked) {
case STRING:
return new JsonPrimitive(in.nextString());
case NUMBER:
String number = in.nextString();
return new JsonPrimitive(new LazilyParsedNumber(number));
case BOOLEAN:
return new JsonPrimitive(in.nextBoolean());
case NULL:
in.nextNull();
return JsonNull.INSTANCE;
default:
// When read(JsonReader) is called with JsonReader in invalid state
throw new IllegalStateException("Unexpected token: " + peeked);
}
}
@Override public JsonElement read(JsonReader in) throws IOException {
if (in instanceof JsonTreeReader) {
return ((JsonTreeReader) in).nextJsonElement();
}
switch (in.peek()) {
case STRING:
return new JsonPrimitive(in.nextString());
case NUMBER:
String number = in.nextString();
return new JsonPrimitive(new LazilyParsedNumber(number));
case BOOLEAN:
return new JsonPrimitive(in.nextBoolean());
case NULL:
in.nextNull();
return JsonNull.INSTANCE;
case BEGIN_ARRAY:
JsonArray array = new JsonArray();
in.beginArray();
// Either JsonArray or JsonObject
JsonElement current;
JsonToken peeked = in.peek();
current = tryBeginNesting(in, peeked);
if (current == null) {
return readTerminal(in, peeked);
}
Deque<JsonElement> stack = new ArrayDeque<>();
while (true) {
while (in.hasNext()) {
array.add(read(in));
String name = null;
// Name is only used for JSON object members
if (current instanceof JsonObject) {
name = in.nextName();
}
peeked = in.peek();
JsonElement value = tryBeginNesting(in, peeked);
boolean isNesting = value != null;
if (value == null) {
value = readTerminal(in, peeked);
}
if (current instanceof JsonArray) {
((JsonArray) current).add(value);
} else {
((JsonObject) current).add(name, value);
}
if (isNesting) {
stack.addLast(current);
current = value;
}
}
in.endArray();
return array;
case BEGIN_OBJECT:
JsonObject object = new JsonObject();
in.beginObject();
while (in.hasNext()) {
object.add(in.nextName(), read(in));
// End current element
if (current instanceof JsonArray) {
in.endArray();
} else {
in.endObject();
}
if (stack.isEmpty()) {
return current;
} else {
// Continue with enclosing element
current = stack.removeLast();
}
in.endObject();
return object;
case END_DOCUMENT:
case NAME:
case END_OBJECT:
case END_ARRAY:
default:
throw new IllegalArgumentException();
}
}
@ -803,7 +859,7 @@ public final class TypeAdapters {
T constant = (T)(constantField.get(null));
String name = constant.name();
String toStringVal = constant.toString();
SerializedName annotation = constantField.getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
@ -835,7 +891,6 @@ public final class TypeAdapters {
}
public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() {
@SuppressWarnings({"rawtypes", "unchecked"})
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Class<? super T> rawType = typeToken.getRawType();
if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
@ -844,7 +899,9 @@ public final class TypeAdapters {
if (!rawType.isEnum()) {
rawType = rawType.getSuperclass(); // handle anonymous subclasses
}
return (TypeAdapter<T>) new EnumTypeAdapter(rawType);
@SuppressWarnings({"rawtypes", "unchecked"})
TypeAdapter<T> adapter = (TypeAdapter<T>) new EnumTypeAdapter(rawType);
return adapter;
}
};

View File

@ -2,7 +2,11 @@ package com.google.gson.internal.bind.util;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.*;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* Utilities methods for manipulating dates in iso8601 format. This is much much faster and GC friendly than using SimpleDateFormat so
@ -147,9 +151,10 @@ public class ISO8601Utils
// 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();

View File

@ -17,13 +17,13 @@
package com.google.gson.reflect;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.$Gson$Preconditions;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Represents a generic type {@code T}. Java doesn't yet provide a way to
@ -72,7 +72,7 @@ public class TypeToken<T> {
*/
@SuppressWarnings("unchecked")
private TypeToken(Type type) {
this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type));
this.type = $Gson$Types.canonicalize(Objects.requireNonNull(type));
this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type);
this.hashCode = this.type.hashCode();
}
@ -319,8 +319,46 @@ public class TypeToken<T> {
/**
* Gets type literal for the parameterized type represented by applying {@code typeArguments} to
* {@code rawType}.
*
* @throws IllegalArgumentException
* If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for
* the raw type
*/
public static TypeToken<?> getParameterized(Type rawType, Type... typeArguments) {
Objects.requireNonNull(rawType);
Objects.requireNonNull(typeArguments);
// Perform basic validation here because this is the only public API where users
// can create malformed parameterized types
if (!(rawType instanceof Class)) {
// See also https://bugs.openjdk.org/browse/JDK-8250659
throw new IllegalArgumentException("rawType must be of type Class, but was " + rawType);
}
Class<?> rawClass = (Class<?>) rawType;
TypeVariable<?>[] typeVariables = rawClass.getTypeParameters();
int expectedArgsCount = typeVariables.length;
int actualArgsCount = typeArguments.length;
if (actualArgsCount != expectedArgsCount) {
throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount +
" type arguments, but got " + actualArgsCount);
}
for (int i = 0; i < expectedArgsCount; i++) {
Type typeArgument = typeArguments[i];
Class<?> rawTypeArgument = $Gson$Types.getRawType(typeArgument);
TypeVariable<?> typeVariable = typeVariables[i];
for (Type bound : typeVariable.getBounds()) {
Class<?> rawBound = $Gson$Types.getRawType(bound);
if (!rawBound.isAssignableFrom(rawTypeArgument)) {
throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds "
+ "for type variable " + typeVariable + " declared by " + rawType);
}
}
}
return new TypeToken<>($Gson$Types.newParameterizedTypeWithOwner(null, rawType, typeArguments));
}

View File

@ -23,6 +23,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.Objects;
/**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>)
@ -287,10 +288,7 @@ public class JsonReader implements Closeable {
* Creates a new instance that reads a JSON-encoded stream from {@code in}.
*/
public JsonReader(Reader in) {
if (in == null) {
throw new NullPointerException("in == null");
}
this.in = in;
this.in = Objects.requireNonNull(in, "in == null");
}
/**
@ -304,8 +302,6 @@ public class JsonReader implements Closeable {
* prefix</a>, <code>")]}'\n"</code>.
* <li>Streams that include multiple top-level values. With strict parsing,
* each stream must contain exactly one top-level value.
* <li>Top-level values of any type. With strict parsing, the top-level
* value must be an object or an array.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* <li>End of line comments starting with {@code //} or {@code #} and
@ -320,6 +316,18 @@ public class JsonReader implements Closeable {
* {@code :}.
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
*
* <p>Note: Even in strict mode there are slight derivations from the JSON
* specification:
* <ul>
* <li>JsonReader allows the literals {@code true}, {@code false} and {@code null}
* to have any capitalization, for example {@code fAlSe}
* <li>JsonReader supports the escape sequence {@code \'}, representing a {@code '}
* <li>JsonReader supports the escape sequence <code>\<i>LF</i></code> (with {@code LF}
* being the Unicode character U+000A), resulting in a {@code LF} within the
* read JSON string
* <li>JsonReader allows unescaped control characters (U+0000 through U+001F)
* </ul>
*/
public final void setLenient(boolean lenient) {
this.lenient = lenient;
@ -457,6 +465,7 @@ public class JsonReader implements Closeable {
}
}
@SuppressWarnings("fallthrough")
int doPeek() throws IOException {
int peekStack = stack[stackSize - 1];
if (peekStack == JsonScope.EMPTY_ARRAY) {
@ -752,6 +761,7 @@ public class JsonReader implements Closeable {
}
}
@SuppressWarnings("fallthrough")
private boolean isLiteral(char c) throws IOException {
switch (c) {
case '/':
@ -891,7 +901,9 @@ public class JsonReader implements Closeable {
*
* @throws IllegalStateException if the next token is not a literal value.
* @throws NumberFormatException if the next literal value cannot be parsed
* as a double, or is non-finite.
* as a double.
* @throws MalformedJsonException if the next literal value is NaN or Infinity
* and this reader is not {@link #setLenient(boolean) lenient}.
*/
public double nextDouble() throws IOException {
int p = peeked;
@ -1130,6 +1142,7 @@ public class JsonReader implements Closeable {
throw syntaxError("Unterminated string");
}
@SuppressWarnings("fallthrough")
private void skipUnquotedValue() throws IOException {
do {
int i = 0;
@ -1546,6 +1559,7 @@ public class JsonReader implements Closeable {
* @throws NumberFormatException if any unicode escape sequences are
* malformed.
*/
@SuppressWarnings("fallthrough")
private char readEscapeCharacter() throws IOException {
if (pos == limit && !fillBuffer(1)) {
throw syntaxError("Unterminated escape sequence");

View File

@ -31,7 +31,7 @@ final class JsonScope {
static final int EMPTY_ARRAY = 1;
/**
* A array with at least one value requires a comma and newline before
* An array with at least one value requires a comma and newline before
* the next element.
*/
static final int NONEMPTY_ARRAY = 2;

View File

@ -16,6 +16,14 @@
package com.google.gson.stream;
import static com.google.gson.stream.JsonScope.DANGLING_NAME;
import static com.google.gson.stream.JsonScope.EMPTY_ARRAY;
import static com.google.gson.stream.JsonScope.EMPTY_DOCUMENT;
import static com.google.gson.stream.JsonScope.EMPTY_OBJECT;
import static com.google.gson.stream.JsonScope.NONEMPTY_ARRAY;
import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT;
import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
import com.google.gson.internal.*;
import java.io.Closeable;
@ -29,14 +37,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import static com.google.gson.stream.JsonScope.DANGLING_NAME;
import static com.google.gson.stream.JsonScope.EMPTY_ARRAY;
import static com.google.gson.stream.JsonScope.EMPTY_DOCUMENT;
import static com.google.gson.stream.JsonScope.EMPTY_OBJECT;
import static com.google.gson.stream.JsonScope.NONEMPTY_ARRAY;
import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT;
import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
/**
* Writes a JSON (<a href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>)
* encoded value to a stream, one token at a time. The stream includes both
@ -44,10 +44,9 @@ import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
* and end delimiters of objects and arrays.
*
* <h3>Encoding JSON</h3>
* To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
* document must contain one top-level array or object. Call methods on the
* writer as you walk the structure's contents, nesting arrays and objects as
* necessary:
* 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
* as necessary:
* <ul>
* <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
* Write each of the array's elements with the appropriate {@link #value}
@ -155,7 +154,7 @@ public class JsonWriter implements Closeable, Flushable {
static {
REPLACEMENT_CHARS = new String[128];
for (int i = 0; i <= 0x1f; i++) {
REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i);
REPLACEMENT_CHARS[i] = String.format("\\u%04x", i);
}
REPLACEMENT_CHARS['"'] = "\\\"";
REPLACEMENT_CHARS['\\'] = "\\\\";
@ -172,7 +171,7 @@ public class JsonWriter implements Closeable, Flushable {
HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";
}
/** The output data, containing at most one top-level array or object. */
/** The JSON output destination */
private final Writer out;
private int[] stack = new int[32];
@ -210,10 +209,7 @@ public class JsonWriter implements Closeable, Flushable {
* {@link java.io.BufferedWriter BufferedWriter} if necessary.
*/
public JsonWriter(Writer out) {
if (out == null) {
throw new NullPointerException("out == null");
}
this.out = out;
this.out = Objects.requireNonNull(out, "out == null");
}
/**
@ -240,8 +236,6 @@ public class JsonWriter implements Closeable, Flushable {
* href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer
* to lenient permits the following:
* <ul>
* <li>Top-level values of any type. With strict writing, the top-level
* value must be an object or an array.
* <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
* Double#isInfinite() infinities}.
* </ul>
@ -447,9 +441,7 @@ public class JsonWriter implements Closeable, Flushable {
* @return this writer.
*/
public JsonWriter name(String name) throws IOException {
if (name == null) {
throw new NullPointerException("name == null");
}
Objects.requireNonNull(name, "name == null");
if (deferredName != null) {
throw new IllegalStateException();
}
@ -491,10 +483,13 @@ public class JsonWriter implements Closeable, Flushable {
/**
* Writes {@code value} directly to the writer without quoting or
* escaping.
* 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.
*/
public JsonWriter jsonValue(String value) throws IOException {
if (value == null) {
@ -555,8 +550,28 @@ public class JsonWriter implements Closeable, Flushable {
/**
* Encodes {@code value}.
*
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @param value a finite value, or if {@link #setLenient(boolean) lenient},
* also {@link Float#isNaN() NaN} or {@link Float#isInfinite()
* infinity}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is not {@link
* #setLenient(boolean) lenient}.
*/
public JsonWriter value(float value) throws IOException {
writeDeferredName();
if (!lenient && (Float.isNaN(value) || Float.isInfinite(value))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
beforeValue();
out.append(Float.toString(value));
return this;
}
/**
* Encodes {@code value}.
*
* @param value a finite value, or if {@link #setLenient(boolean) lenient},
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}.
@ -598,8 +613,8 @@ public class JsonWriter implements Closeable, Flushable {
* Encodes {@code value}. The value is written by directly writing the {@link Number#toString()}
* result to JSON. Implementations must make sure that the result represents a valid JSON number.
*
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @param value a finite value, or if {@link #setLenient(boolean) lenient},
* also {@link Double#isNaN() NaN} or {@link Double#isInfinite() infinity}.
* @return this writer.
* @throws IllegalArgumentException if the value is NaN or Infinity and this writer is
* not {@link #setLenient(boolean) lenient}; or if the {@code toString()} result is not a

View File

@ -17,12 +17,10 @@
package com.google.gson;
import com.google.gson.reflect.TypeToken;
import junit.framework.TestCase;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.List;
import junit.framework.TestCase;
/**
* Unit tests for the {@link FieldAttributes} class.
@ -62,10 +60,6 @@ public class FieldAttributesTest extends TestCase {
assertTrue(fieldAttributes.hasModifier(Modifier.TRANSIENT));
}
public void testIsSynthetic() throws Exception {
assertFalse(fieldAttributes.isSynthetic());
}
public void testName() throws Exception {
assertEquals("bar", fieldAttributes.getName());
}

View File

@ -16,13 +16,13 @@
package com.google.gson;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import junit.framework.TestCase;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import junit.framework.TestCase;
/**
* Unit tests for {@link GsonBuilder}.
@ -41,8 +41,99 @@ public class GsonBuilderTest extends TestCase {
public void testCreatingMoreThanOnce() {
GsonBuilder builder = new GsonBuilder();
builder.create();
builder.create();
Gson gson = builder.create();
assertNotNull(gson);
assertNotNull(builder.create());
builder.setFieldNamingStrategy(new FieldNamingStrategy() {
@Override public String translateName(Field f) {
return "test";
}
});
Gson otherGson = builder.create();
assertNotNull(otherGson);
// Should be different instances because builder has been modified in the meantime
assertNotSame(gson, otherGson);
}
/**
* Gson instances should not be affected by subsequent modification of GsonBuilder
* which created them.
*/
public void testModificationAfterCreate() {
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
// Modifications of `gsonBuilder` should not affect `gson` object
gsonBuilder.registerTypeAdapter(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 {
out.value("custom-adapter");
}
});
gsonBuilder.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
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);
// New GsonBuilder created from `gson` should not have been affected by changes
// to `gsonBuilder` either
assertDefaultGson(gson.newBuilder().create());
// New Gson instance from modified GsonBuilder should be affected by changes
assertCustomGson(gsonBuilder.create());
}
private static void assertDefaultGson(Gson gson) {
// Should use default reflective adapter
String json1 = gson.toJson(new CustomClass1());
assertEquals("{}", json1);
// Should use default reflective adapter
String json2 = gson.toJson(new CustomClass2());
assertEquals("{}", json2);
// Should use default instance creator
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
assertEquals(CustomClass3.NO_ARG_CONSTRUCTOR_VALUE, customClass3.s);
}
private static void assertCustomGson(Gson gson) {
String json1 = gson.toJson(new CustomClass1());
assertEquals("\"custom-adapter\"", json1);
String json2 = gson.toJson(new CustomClass2());
assertEquals("\"custom-hierarchy-adapter\"", json2);
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
assertEquals("custom-instance", customClass3.s);
}
static class CustomClass1 { }
static class CustomClass2 { }
static class CustomClass3 {
static final String NO_ARG_CONSTRUCTOR_VALUE = "default instance";
final String s;
public CustomClass3(String s) {
this.s = s;
}
public CustomClass3() {
this(NO_ARG_CONSTRUCTOR_VALUE);
}
}
public void testExcludeFieldsWithModifiers() {
@ -52,20 +143,6 @@ public class GsonBuilderTest extends TestCase {
assertEquals("{\"d\":\"d\"}", gson.toJson(new HasModifiers()));
}
public void testRegisterTypeAdapterForCoreType() {
Type[] types = {
byte.class,
int.class,
double.class,
Short.class,
Long.class,
String.class,
};
for (Type type : types) {
new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
}
}
@SuppressWarnings("unused")
static class HasModifiers {
private String a = "a";
@ -85,6 +162,20 @@ public class GsonBuilderTest extends TestCase {
transient String a = "a";
}
public void testRegisterTypeAdapterForCoreType() {
Type[] types = {
byte.class,
int.class,
double.class,
Short.class,
Long.class,
String.class,
};
for (Type type : types) {
new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
}
}
public void testDisableJdkUnsafe() {
Gson gson = new GsonBuilder()
.disableJdkUnsafe()

View File

@ -17,6 +17,7 @@
package com.google.gson;
import com.google.gson.internal.Excluder;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.gson.stream.MalformedJsonException;
@ -29,6 +30,8 @@ import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import junit.framework.TestCase;
/**
@ -89,6 +92,69 @@ public final class GsonTest extends TestCase {
@Override public Object read(JsonReader in) throws IOException { return null; }
}
public void testGetAdapter_Null() {
Gson gson = new Gson();
try {
gson.getAdapter((TypeToken<?>) null);
fail();
} catch (NullPointerException e) {
assertEquals("type must not be null", e.getMessage());
}
}
public void testGetAdapter_Concurrency() {
class DummyAdapter<T> extends TypeAdapter<T> {
@Override public void write(JsonWriter out, T value) throws IOException {
throw new AssertionError("not needed for test");
}
@Override public T read(JsonReader in) throws IOException {
throw new AssertionError("not needed for test");
}
}
final AtomicInteger adapterInstancesCreated = new AtomicInteger(0);
final AtomicReference<TypeAdapter<?>> threadAdapter = new AtomicReference<>();
final Class<?> requestedType = Number.class;
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
private volatile boolean isFirstCall = true;
@Override public <T> TypeAdapter<T> create(final Gson gson, TypeToken<T> type) {
if (isFirstCall) {
isFirstCall = false;
// Create a separate thread which requests an adapter for the same type
// This will cause this factory to return a different adapter instance than
// the one it is currently creating
Thread thread = new Thread() {
@Override public void run() {
threadAdapter.set(gson.getAdapter(requestedType));
}
};
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// Create a new dummy adapter instance
adapterInstancesCreated.incrementAndGet();
return new DummyAdapter<>();
}
})
.create();
TypeAdapter<?> adapter = gson.getAdapter(requestedType);
assertTrue(adapter instanceof DummyAdapter);
assertEquals(2, adapterInstancesCreated.get());
// Should be the same adapter instance the concurrent thread received
assertSame(threadAdapter.get(), adapter);
}
public void testNewJsonWriter_Default() throws IOException {
StringWriter writer = new StringWriter();
JsonWriter jsonWriter = new GsonBuilder().create().newJsonWriter(writer);
@ -155,4 +221,151 @@ public final class GsonTest extends TestCase {
assertEquals("test", jsonReader.nextString());
jsonReader.close();
}
/**
* Modifying a GsonBuilder obtained from {@link Gson#newBuilder()} of a
* {@code new Gson()} should not affect the Gson instance it came from.
*/
public void testDefaultGsonNewBuilderModification() {
Gson gson = new Gson();
GsonBuilder gsonBuilder = gson.newBuilder();
// Modifications of `gsonBuilder` should not affect `gson` object
gsonBuilder.registerTypeAdapter(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 {
out.value("custom-adapter");
}
});
gsonBuilder.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
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);
// New GsonBuilder created from `gson` should not have been affected by changes either
assertDefaultGson(gson.newBuilder().create());
// But new Gson instance from `gsonBuilder` should use custom adapters
assertCustomGson(gsonBuilder.create());
}
private static void assertDefaultGson(Gson gson) {
// Should use default reflective adapter
String json1 = gson.toJson(new CustomClass1());
assertEquals("{}", json1);
// Should use default reflective adapter
String json2 = gson.toJson(new CustomClass2());
assertEquals("{}", json2);
// Should use default instance creator
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
assertEquals(CustomClass3.NO_ARG_CONSTRUCTOR_VALUE, customClass3.s);
}
/**
* Modifying a GsonBuilder obtained from {@link Gson#newBuilder()} of a custom
* Gson instance (created using a GsonBuilder) should not affect the Gson instance
* it came from.
*/
public void testNewBuilderModification() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(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 {
out.value("custom-adapter");
}
})
.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive("custom-hierarchy-adapter");
}
})
.registerTypeAdapter(CustomClass3.class, new InstanceCreator<CustomClass3>() {
@Override public CustomClass3 createInstance(Type type) {
return new CustomClass3("custom-instance");
}
})
.create();
assertCustomGson(gson);
// Modify `gson.newBuilder()`
GsonBuilder gsonBuilder = gson.newBuilder();
gsonBuilder.registerTypeAdapter(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 {
out.value("overwritten custom-adapter");
}
});
gsonBuilder.registerTypeHierarchyAdapter(CustomClass2.class, new JsonSerializer<CustomClass2>() {
@Override public JsonElement serialize(CustomClass2 src, Type typeOfSrc, JsonSerializationContext context) {
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
assertCustomGson(gson);
// New GsonBuilder based on `gson` should not have been affected either
assertCustomGson(gson.newBuilder().create());
// But new Gson instance from `gsonBuilder` should be affected by changes
Gson otherGson = gsonBuilder.create();
String json1 = otherGson.toJson(new CustomClass1());
assertEquals("\"overwritten custom-adapter\"", json1);
String json2 = otherGson.toJson(new CustomClass2());
assertEquals("\"overwritten custom-hierarchy-adapter\"", json2);
CustomClass3 customClass3 = otherGson.fromJson("{}", CustomClass3.class);
assertEquals("overwritten custom-instance", customClass3.s);
}
private static void assertCustomGson(Gson gson) {
String json1 = gson.toJson(new CustomClass1());
assertEquals("\"custom-adapter\"", json1);
String json2 = gson.toJson(new CustomClass2());
assertEquals("\"custom-hierarchy-adapter\"", json2);
CustomClass3 customClass3 = gson.fromJson("{}", CustomClass3.class);
assertEquals("custom-instance", customClass3.s);
}
static class CustomClass1 { }
static class CustomClass2 { }
static class CustomClass3 {
static final String NO_ARG_CONSTRUCTOR_VALUE = "default instance";
final String s;
public CustomClass3(String s) {
this.s = s;
}
public CustomClass3() {
this(NO_ARG_CONSTRUCTOR_VALUE);
}
}
}

View File

@ -53,10 +53,16 @@ public class GsonTypeAdapterTest extends TestCase {
fail("Type Adapter should have thrown an exception");
} catch (IllegalStateException expected) { }
// Verify that serializer is made null-safe, i.e. it is not called for null
assertEquals("null", gson.toJson(null, AtomicLong.class));
try {
gson.fromJson("123", AtomicLong.class);
fail("Type Adapter should have thrown an exception");
} catch (JsonParseException expected) { }
// Verify that deserializer is made null-safe, i.e. it is not called for null
assertNull(gson.fromJson(JsonNull.INSTANCE, AtomicLong.class));
}
public void testTypeAdapterProperlyConvertsTypes() throws Exception {

View File

@ -75,11 +75,18 @@ public final class JsonArrayTest extends TestCase {
} catch (IndexOutOfBoundsException expected) {}
JsonPrimitive a = new JsonPrimitive("a");
array.add(a);
array.set(0, new JsonPrimitive("b"));
JsonPrimitive b = new JsonPrimitive("b");
JsonElement oldValue = array.set(0, b);
assertEquals(a, oldValue);
assertEquals("b", array.get(0).getAsString());
array.set(0, null);
assertNull(array.get(0));
array.set(0, new JsonPrimitive("c"));
oldValue = array.set(0, null);
assertEquals(b, oldValue);
assertEquals(JsonNull.INSTANCE, array.get(0));
oldValue = array.set(0, new JsonPrimitive("c"));
assertEquals(JsonNull.INSTANCE, oldValue);
assertEquals("c", array.get(0).getAsString());
assertEquals(1, array.size());
}
@ -99,6 +106,18 @@ public final class JsonArrayTest extends TestCase {
assertEquals(0, copy.get(0).getAsJsonArray().size());
}
public void testIsEmpty() {
JsonArray array = new JsonArray();
assertTrue(array.isEmpty());
JsonPrimitive a = new JsonPrimitive("a");
array.add(a);
assertFalse(array.isEmpty());
array.remove(0);
assertTrue(array.isEmpty());
}
public void testFailedGetArrayValues() {
JsonArray jsonArray = new JsonArray();
jsonArray.add(JsonParser.parseString("{" + "\"key1\":\"value1\"," + "\"key2\":\"value2\"," + "\"key3\":\"value3\"," + "\"key4\":\"value4\"" + "}"));
@ -162,4 +181,23 @@ public final class JsonArrayTest extends TestCase {
"For input string: \"hello\"", e.getMessage());
}
}
public void testGetAs_WrongArraySize() {
JsonArray jsonArray = new JsonArray();
try {
jsonArray.getAsByte();
fail();
} catch (IllegalStateException e) {
assertEquals("Array must have size 1, but has size 0", e.getMessage());
}
jsonArray.add(true);
jsonArray.add(false);
try {
jsonArray.getAsByte();
fail();
} catch (IllegalStateException e) {
assertEquals("Array must have size 1, but has size 2", e.getMessage());
}
}
}

View File

@ -17,7 +17,16 @@
package com.google.gson;
import com.google.gson.common.MoreAsserts;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import junit.framework.TestCase;
/**
@ -41,6 +50,8 @@ public class JsonObjectTest extends TestCase {
assertEquals(value, removedElement);
assertFalse(jsonObj.has(propertyName));
assertNull(jsonObj.get(propertyName));
assertNull(jsonObj.remove(propertyName));
}
public void testAddingNullPropertyValue() throws Exception {
@ -161,6 +172,22 @@ public class JsonObjectTest extends TestCase {
assertFalse(b.equals(a));
}
public void testEqualsHashCodeIgnoringOrder() {
JsonObject a = new JsonObject();
JsonObject b = new JsonObject();
a.addProperty("1", true);
b.addProperty("2", false);
a.addProperty("2", false);
b.addProperty("1", true);
assertEquals(Arrays.asList("1", "2"), new ArrayList<>(a.keySet()));
assertEquals(Arrays.asList("2", "1"), new ArrayList<>(b.keySet()));
MoreAsserts.assertEqualsAndHashCode(a, b);
}
public void testSize() {
JsonObject o = new JsonObject();
assertEquals(0, o.size());
@ -192,6 +219,7 @@ public class JsonObjectTest extends TestCase {
*/
public void testKeySet() {
JsonObject a = new JsonObject();
assertEquals(0, a.keySet().size());
a.add("foo", new JsonArray());
a.add("bar", new JsonObject());
@ -200,5 +228,94 @@ public class JsonObjectTest extends TestCase {
assertEquals(2, a.keySet().size());
assertTrue(a.keySet().contains("foo"));
assertTrue(a.keySet().contains("bar"));
a.addProperty("1", true);
a.addProperty("2", false);
// Insertion order should be preserved by keySet()
Deque<String> expectedKeys = new ArrayDeque<>(Arrays.asList("foo", "bar", "1", "2"));
// Note: Must wrap in ArrayList because Deque implementations do not implement `equals`
assertEquals(new ArrayList<>(expectedKeys), new ArrayList<>(a.keySet()));
Iterator<String> iterator = a.keySet().iterator();
// Remove keys one by one
for (int i = a.size(); i >= 1; i--) {
assertTrue(iterator.hasNext());
assertEquals(expectedKeys.getFirst(), iterator.next());
iterator.remove();
expectedKeys.removeFirst();
assertEquals(i - 1, a.size());
assertEquals(new ArrayList<>(expectedKeys), new ArrayList<>(a.keySet()));
}
}
public void testEntrySet() {
JsonObject o = new JsonObject();
assertEquals(0, o.entrySet().size());
o.addProperty("b", true);
Set<?> expectedEntries = Collections.singleton(new SimpleEntry<>("b", new JsonPrimitive(true)));
assertEquals(expectedEntries, o.entrySet());
assertEquals(1, o.entrySet().size());
o.addProperty("a", false);
// Insertion order should be preserved by entrySet()
List<?> expectedEntriesList = Arrays.asList(
new SimpleEntry<>("b", new JsonPrimitive(true)),
new SimpleEntry<>("a", new JsonPrimitive(false))
);
assertEquals(expectedEntriesList, new ArrayList<>(o.entrySet()));
Iterator<Entry<String, JsonElement>> iterator = o.entrySet().iterator();
// Test behavior of Entry.setValue
for (int i = 0; i < o.size(); i++) {
Entry<String, JsonElement> entry = iterator.next();
entry.setValue(new JsonPrimitive(i));
assertEquals(new JsonPrimitive(i), entry.getValue());
}
expectedEntriesList = Arrays.asList(
new SimpleEntry<>("b", new JsonPrimitive(0)),
new SimpleEntry<>("a", new JsonPrimitive(1))
);
assertEquals(expectedEntriesList, new ArrayList<>(o.entrySet()));
Entry<String, JsonElement> entry = o.entrySet().iterator().next();
try {
// null value is not permitted, only JsonNull is supported
// This intentionally deviates from the behavior of the other JsonObject methods which
// implicitly convert null -> JsonNull, to match more closely the contract of Map.Entry
entry.setValue(null);
fail();
} catch (NullPointerException e) {
assertEquals("value == null", e.getMessage());
}
assertNotNull(entry.getValue());
o.addProperty("key1", 1);
o.addProperty("key2", 2);
Deque<?> expectedEntriesQueue = new ArrayDeque<>(Arrays.asList(
new SimpleEntry<>("b", new JsonPrimitive(0)),
new SimpleEntry<>("a", new JsonPrimitive(1)),
new SimpleEntry<>("key1", new JsonPrimitive(1)),
new SimpleEntry<>("key2", new JsonPrimitive(2))
));
// Note: Must wrap in ArrayList because Deque implementations do not implement `equals`
assertEquals(new ArrayList<>(expectedEntriesQueue), new ArrayList<>(o.entrySet()));
iterator = o.entrySet().iterator();
// Remove entries one by one
for (int i = o.size(); i >= 1; i--) {
assertTrue(iterator.hasNext());
assertEquals(expectedEntriesQueue.getFirst(), iterator.next());
iterator.remove();
expectedEntriesQueue.removeFirst();
assertEquals(i - 1, o.size());
assertEquals(new ArrayList<>(expectedEntriesQueue), new ArrayList<>(o.entrySet()));
}
}
}

View File

@ -0,0 +1,41 @@
package com.google.gson;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class JsonParserParameterizedTest {
@Parameters
public static Iterable<String> data() {
return Arrays.asList(
"[]",
"{}",
"null",
"1.0",
"true",
"\"string\"",
"[true,1.0,null,{},2.0,{\"a\":[false]},[3.0,\"test\"],4.0]",
"{\"\":1.0,\"a\":true,\"b\":null,\"c\":[],\"d\":{\"a1\":2.0,\"b2\":[true,{\"a3\":3.0}]},\"e\":[{\"f\":4.0},\"test\"]}"
);
}
private final TypeAdapter<JsonElement> adapter = new Gson().getAdapter(JsonElement.class);
@Parameter
public String json;
@Test
public void testParse() throws IOException {
JsonElement deserialized = JsonParser.parseString(json);
String actualSerialized = adapter.toJson(deserialized);
// Serialized JsonElement should be the same as original JSON
assertEquals(json, actualSerialized);
}
}

View File

@ -18,8 +18,8 @@ package com.google.gson;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.StringReader;
import junit.framework.TestCase;
import com.google.gson.common.TestTypes.BagOfPrimitives;
@ -90,6 +90,54 @@ public class JsonParserTest extends TestCase {
assertEquals("stringValue", array.get(2).getAsString());
}
private static String repeat(String s, int times) {
StringBuilder stringBuilder = new StringBuilder(s.length() * times);
for (int i = 0; i < times; i++) {
stringBuilder.append(s);
}
return stringBuilder.toString();
}
/** Deeply nested JSON arrays should not cause {@link StackOverflowError} */
public void testParseDeeplyNestedArrays() throws IOException {
int times = 10000;
// [[[ ... ]]]
String json = repeat("[", times) + repeat("]", times);
int actualTimes = 0;
JsonArray current = JsonParser.parseString(json).getAsJsonArray();
while (true) {
actualTimes++;
if (current.isEmpty()) {
break;
}
assertEquals(1, current.size());
current = current.get(0).getAsJsonArray();
}
assertEquals(times, actualTimes);
}
/** Deeply nested JSON objects should not cause {@link StackOverflowError} */
public void testParseDeeplyNestedObjects() throws IOException {
int times = 10000;
// {"a":{"a": ... {"a":null} ... }}
String json = repeat("{\"a\":", times) + "null" + repeat("}", times);
int actualTimes = 0;
JsonObject current = JsonParser.parseString(json).getAsJsonObject();
while (true) {
assertEquals(1, current.size());
actualTimes++;
JsonElement next = current.get("a");
if (next.isJsonNull()) {
break;
} else {
current = next.getAsJsonObject();
}
}
assertEquals(times, actualTimes);
}
public void testParseReader() {
StringReader reader = new StringReader("{a:10,b:'c'}");
JsonElement e = JsonParser.parseReader(reader);

View File

@ -17,11 +17,9 @@
package com.google.gson;
import com.google.gson.common.MoreAsserts;
import junit.framework.TestCase;
import java.math.BigDecimal;
import java.math.BigInteger;
import junit.framework.TestCase;
/**
* Unit test for the {@link JsonPrimitive} class.
@ -98,6 +96,17 @@ public class JsonPrimitiveTest extends TestCase {
assertEquals(new BigDecimal("1"), json.getAsBigDecimal());
}
public void testAsNumber_Boolean() {
JsonPrimitive json = new JsonPrimitive(true);
try {
json.getAsNumber();
fail();
} catch (UnsupportedOperationException e) {
assertEquals("Primitive is neither a number nor a string", e.getMessage());
}
}
@SuppressWarnings("deprecation")
public void testStringsAndChar() throws Exception {
JsonPrimitive json = new JsonPrimitive("abc");
assertTrue(json.isString());
@ -111,6 +120,15 @@ public class JsonPrimitiveTest extends TestCase {
json = new JsonPrimitive(true);
assertEquals("true", json.getAsString());
json = new JsonPrimitive("");
assertEquals("", json.getAsString());
try {
json.getAsCharacter();
fail();
} catch (UnsupportedOperationException e) {
assertEquals("String value is empty", e.getMessage());
}
}
public void testExponential() throws Exception {
@ -256,7 +274,7 @@ public class JsonPrimitiveTest extends TestCase {
public void testEqualsIntegerAndBigInteger() {
JsonPrimitive a = new JsonPrimitive(5L);
JsonPrimitive b = new JsonPrimitive(new BigInteger("18446744073709551621")); // 2^64 + 5
// Ideally, the following assertion should have failed but the price is too much to pay
// Ideally, the following assertion should have failed but the price is too much to pay
// assertFalse(a + " equals " + b, a.equals(b));
assertTrue(a + " equals " + b, a.equals(b));
}

View File

@ -0,0 +1,41 @@
package com.google.gson;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ObjectTypeAdapterParameterizedTest {
@Parameters
public static Iterable<String> data() {
return Arrays.asList(
"[]",
"{}",
"null",
"1.0",
"true",
"\"string\"",
"[true,1.0,null,{},2.0,{\"a\":[false]},[3.0,\"test\"],4.0]",
"{\"\":1.0,\"a\":true,\"b\":null,\"c\":[],\"d\":{\"a1\":2.0,\"b2\":[true,{\"a3\":3.0}]},\"e\":[{\"f\":4.0},\"test\"]}"
);
}
private final TypeAdapter<Object> adapter = new Gson().getAdapter(Object.class);
@Parameter
public String json;
@Test
public void testReadWrite() throws IOException {
Object deserialized = adapter.fromJson(json);
String actualSerialized = adapter.toJson(deserialized);
// Serialized Object should be the same as original JSON
assertEquals(json, actualSerialized);
}
}

View File

@ -16,9 +16,11 @@
package com.google.gson;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
@ -38,7 +40,7 @@ public final class ObjectTypeAdapterTest extends TestCase {
Object object = new RuntimeType();
assertEquals("{'a':5,'b':[1,2,null]}", adapter.toJson(object).replace("\"", "'"));
}
public void testSerializeNullValue() throws Exception {
Map<String, Object> map = new LinkedHashMap<>();
map.put("a", null);
@ -55,6 +57,51 @@ public final class ObjectTypeAdapterTest extends TestCase {
assertEquals("{}", adapter.toJson(new Object()));
}
private static String repeat(String s, int times) {
StringBuilder stringBuilder = new StringBuilder(s.length() * times);
for (int i = 0; i < times; i++) {
stringBuilder.append(s);
}
return stringBuilder.toString();
}
/** Deeply nested JSON arrays should not cause {@link StackOverflowError} */
@SuppressWarnings("unchecked")
public void testDeserializeDeeplyNestedArrays() throws IOException {
int times = 10000;
// [[[ ... ]]]
String json = repeat("[", times) + repeat("]", times);
int actualTimes = 0;
List<List<?>> current = (List<List<?>>) adapter.fromJson(json);
while (true) {
actualTimes++;
if (current.isEmpty()) {
break;
}
assertEquals(1, current.size());
current = (List<List<?>>) current.get(0);
}
assertEquals(times, actualTimes);
}
/** Deeply nested JSON objects should not cause {@link StackOverflowError} */
@SuppressWarnings("unchecked")
public void testDeserializeDeeplyNestedObjects() throws IOException {
int times = 10000;
// {"a":{"a": ... {"a":null} ... }}
String json = repeat("{\"a\":", times) + "null" + repeat("}", times);
int actualTimes = 0;
Map<String, Map<?, ?>> current = (Map<String, Map<?, ?>>) adapter.fromJson(json);
while (current != null) {
assertEquals(1, current.size());
actualTimes++;
current = (Map<String, Map<?, ?>>) current.get("a");
}
assertEquals(times, actualTimes);
}
@SuppressWarnings("unused")
private class RuntimeType {
Object a = 5;

View File

@ -0,0 +1,85 @@
package com.google.gson;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import org.junit.Test;
public class TypeAdapterTest {
@Test
public void testNullSafe() throws IOException {
TypeAdapter<String> adapter = new TypeAdapter<String>() {
@Override public void write(JsonWriter out, String value) {
throw new AssertionError("unexpected call");
}
@Override public String read(JsonReader in) {
throw new AssertionError("unexpected call");
}
}.nullSafe();
assertEquals("null", adapter.toJson(null));
assertNull(adapter.fromJson("null"));
}
/**
* Tests behavior when {@link TypeAdapter#write(JsonWriter, Object)} manually throws
* {@link IOException} which is not caused by writer usage.
*/
@Test
public void testToJson_ThrowingIOException() {
final IOException exception = new IOException("test");
TypeAdapter<Integer> adapter = new TypeAdapter<Integer>() {
@Override public void write(JsonWriter out, Integer value) throws IOException {
throw exception;
}
@Override public Integer read(JsonReader in) throws IOException {
throw new AssertionError("not needed by this test");
}
};
try {
adapter.toJson(1);
fail();
} catch (JsonIOException e) {
assertEquals(exception, e.getCause());
}
try {
adapter.toJsonTree(1);
fail();
} catch (JsonIOException e) {
assertEquals(exception, e.getCause());
}
}
private static final TypeAdapter<String> adapter = new TypeAdapter<String>() {
@Override public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
@Override public String read(JsonReader in) throws IOException {
return in.nextString();
}
};
// Note: This test just verifies the current behavior; it is a bit questionable
// whether that behavior is actually desired
@Test
public void testFromJson_Reader_TrailingData() throws IOException {
assertEquals("a", adapter.fromJson(new StringReader("\"a\"1")));
}
// Note: This test just verifies the current behavior; it is a bit questionable
// whether that behavior is actually desired
@Test
public void testFromJson_String_TrailingData() throws IOException {
assertEquals("a", adapter.fromJson("\"a\"1"));
}
}

View File

@ -16,9 +16,13 @@
package com.google.gson.common;
import org.junit.Assert;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
/**
* Handy asserts that we wish were present in {@link Assert}
@ -49,4 +53,53 @@ public class MoreAsserts {
Assert.assertFalse(a.equals(null));
Assert.assertFalse(a.equals(new Object()));
}
private static boolean isProtectedOrPublic(Method method) {
int modifiers = method.getModifiers();
return Modifier.isProtected(modifiers) || Modifier.isPublic(modifiers);
}
private static String getMethodSignature(Method method) {
StringBuilder builder = new StringBuilder(method.getName());
builder.append('(');
String sep = "";
for (Class<?> paramType : method.getParameterTypes()) {
builder.append(sep).append(paramType.getName());
sep = ",";
}
builder.append(')');
return builder.toString();
}
/**
* Asserts that {@code subClass} overrides all protected and public methods declared by
* {@code baseClass} except for the ones whose signatures are in {@code ignoredMethods}.
*/
public static void assertOverridesMethods(Class<?> baseClass, Class<?> subClass, List<String> ignoredMethods) {
Set<String> requiredOverriddenMethods = new LinkedHashSet<>();
for (Method method : baseClass.getDeclaredMethods()) {
// Note: Do not filter out `final` methods; maybe they should not be `final` and subclass needs
// to override them
if (isProtectedOrPublic(method)) {
requiredOverriddenMethods.add(getMethodSignature(method));
}
}
for (Method method : subClass.getDeclaredMethods()) {
requiredOverriddenMethods.remove(getMethodSignature(method));
}
for (String ignoredMethod : ignoredMethods) {
boolean foundIgnored = requiredOverriddenMethods.remove(ignoredMethod);
if (!foundIgnored) {
throw new IllegalArgumentException("Method '" + ignoredMethod + "' does not exist or is already overridden");
}
}
if (!requiredOverriddenMethods.isEmpty()) {
Assert.fail(subClass.getSimpleName() + " must override these methods: " + requiredOverriddenMethods);
}
}
}

View File

@ -16,9 +16,6 @@
package com.google.gson.common;
import java.lang.reflect.Type;
import java.util.Collection;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@ -28,6 +25,8 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.SerializedName;
import java.lang.reflect.Type;
import java.util.Collection;
/**
* Types used for testing JSON serialization and deserialization
@ -36,7 +35,7 @@ import com.google.gson.annotations.SerializedName;
* @author Joel Leitch
*/
public class TestTypes {
public static class Base {
public static final String BASE_NAME = Base.class.getSimpleName();
public static final String BASE_FIELD_KEY = "baseName";
@ -76,7 +75,7 @@ public class TestTypes {
}
public static class BaseSerializer implements JsonSerializer<Base> {
public static final String NAME = BaseSerializer.class.getSimpleName();
public static final String NAME = BaseSerializer.class.getSimpleName();
@Override
public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
@ -85,13 +84,13 @@ public class TestTypes {
}
}
public static class SubSerializer implements JsonSerializer<Sub> {
public static final String NAME = SubSerializer.class.getSimpleName();
public static final String NAME = SubSerializer.class.getSimpleName();
@Override
public JsonElement serialize(Sub src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject obj = new JsonObject();
obj.addProperty(Base.SERIALIZER_KEY, NAME);
return obj;
}
}
}
public static class StringWrapper {
@ -228,6 +227,7 @@ public class TestTypes {
}
}
@SuppressWarnings("overrides") // for missing hashCode() override
public static class ClassWithNoFields {
// Nothing here..
@Override
@ -271,7 +271,7 @@ public class TestTypes {
}
public static class ClassWithTransientFields<T> {
public transient T transientT;
public transient T transientT;
public final transient long transientLongValue;
private final long[] longValue;

View File

@ -16,20 +16,19 @@
package com.google.gson.functional;
import static org.junit.Assert.assertArrayEquals;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassWithObjects;
import com.google.gson.reflect.TypeToken;
import junit.framework.TestCase;
import static org.junit.Assert.assertArrayEquals;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import junit.framework.TestCase;
/**
* Functional tests for Json serialization and deserialization of arrays.
*
@ -51,7 +50,7 @@ public class ArrayTest extends TestCase {
}
public void testTopLevelArrayOfIntsDeserialization() {
int[] expected = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] actual = gson.fromJson("[1,2,3,4,5,6,7,8,9]", int[].class);
assertArrayEquals(expected, actual);
}
@ -142,12 +141,12 @@ public class ArrayTest extends TestCase {
assertEquals("hello", arrayType[0]);
}
@SuppressWarnings("unchecked")
public void testArrayOfCollectionSerialization() throws Exception {
StringBuilder sb = new StringBuilder("[");
int arraySize = 3;
Type typeToSerialize = new TypeToken<Collection<Integer>[]>() {}.getType();
@SuppressWarnings({"rawtypes", "unchecked"})
Collection<Integer>[] arrayOfCollection = new ArrayList[arraySize];
for (int i = 0; i < arraySize; ++i) {
int startValue = (3 * i) + 1;
@ -173,8 +172,8 @@ public class ArrayTest extends TestCase {
Collection<Integer>[] target = gson.fromJson(json, type);
assertEquals(2, target.length);
assertArrayEquals(new Integer[] { 1, 2 }, target[0].toArray(new Integer[0]));
assertArrayEquals(new Integer[] { 3, 4 }, target[1].toArray(new Integer[0]));
assertArrayEquals(new Integer[] {1, 2}, target[0].toArray(new Integer[0]));
assertArrayEquals(new Integer[] {3, 4}, target[1].toArray(new Integer[0]));
}
public void testArrayOfPrimitivesAsObjectsSerialization() throws Exception {
@ -201,7 +200,7 @@ public class ArrayTest extends TestCase {
String classWithObjectsJson = gson.toJson(classWithObjects);
String bagOfPrimitivesJson = gson.toJson(bagOfPrimitives);
Object[] objects = new Object[] { classWithObjects, bagOfPrimitives };
Object[] objects = {classWithObjects, bagOfPrimitives};
String json = gson.toJson(objects);
assertTrue(json.contains(classWithObjectsJson));
@ -209,7 +208,7 @@ public class ArrayTest extends TestCase {
}
public void testArrayOfNullSerialization() {
Object[] array = new Object[] {null};
Object[] array = {null};
String json = gson.toJson(array);
assertEquals("[null]", json);
}
@ -222,8 +221,8 @@ public class ArrayTest extends TestCase {
/**
* Regression tests for Issue 272
*/
public void testMultidimenstionalArraysSerialization() {
String[][] items = new String[][]{
public void testMultidimensionalArraysSerialization() {
String[][] items = {
{"3m Co", "71.72", "0.02", "0.03", "4/2 12:00am", "Manufacturing"},
{"Alcoa Inc", "29.01", "0.42", "1.47", "4/1 12:00am", "Manufacturing"}
};
@ -232,23 +231,28 @@ public class ArrayTest extends TestCase {
assertTrue(json.contains("Manufacturing\"]]"));
}
public void testMultiDimenstionalObjectArraysSerialization() {
Object[][] array = new Object[][] { new Object[] { 1, 2 } };
public void testMultidimensionalObjectArraysSerialization() {
Object[][] array = {new Object[] { 1, 2 }};
assertEquals("[[1,2]]", gson.toJson(array));
}
public void testMultidimensionalPrimitiveArraysSerialization() {
int[][] array = {{1, 2}, {3, 4}};
assertEquals("[[1,2],[3,4]]", gson.toJson(array));
}
/**
* Regression test for Issue 205
*/
public void testMixingTypesInObjectArraySerialization() {
Object[] array = new Object[] { 1, 2, new Object[] { "one", "two", 3 } };
Object[] array = {1, 2, new Object[] {"one", "two", 3}};
assertEquals("[1,2,[\"one\",\"two\",3]]", gson.toJson(array));
}
/**
* Regression tests for Issue 272
*/
public void testMultidimenstionalArraysDeserialization() {
public void testMultidimensionalArraysDeserialization() {
String json = "[[\"3m Co\",\"71.72\",\"0.02\",\"0.03\",\"4/2 12:00am\",\"Manufacturing\"],"
+ "[\"Alcoa Inc\",\"29.01\",\"0.42\",\"1.47\",\"4/1 12:00am\",\"Manufacturing\"]]";
String[][] items = gson.fromJson(json, String[][].class);
@ -256,6 +260,12 @@ public class ArrayTest extends TestCase {
assertEquals("Manufacturing", items[1][5]);
}
public void testMultidimensionalPrimitiveArraysDeserialization() {
String json = "[[1,2],[3,4]]";
int[][] expected = {{1, 2}, {3, 4}};
assertArrayEquals(expected, gson.fromJson(json, int[][].class));
}
/** http://code.google.com/p/google-gson/issues/detail?id=342 */
public void testArrayElementsAreArrays() {
Object[] stringArrays = {

View File

@ -16,6 +16,16 @@
package com.google.gson.functional;
import static org.junit.Assert.assertArrayEquals;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
@ -30,18 +40,7 @@ import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import junit.framework.TestCase;
import static org.junit.Assert.assertArrayEquals;
/**
* Functional tests for Json serialization and deserialization of collections.
@ -241,35 +240,33 @@ public class CollectionTest extends TestCase {
assertEquals("[1,2,3,4,5,6,7,8,9]", gson.toJson(target));
}
@SuppressWarnings("rawtypes")
public void testRawCollectionSerialization() {
public void testObjectCollectionSerialization() {
BagOfPrimitives bag1 = new BagOfPrimitives();
Collection target = Arrays.asList(bag1, bag1);
Collection<?> target = Arrays.asList(bag1, bag1, "test");
String json = gson.toJson(target);
assertTrue(json.contains(bag1.getExpectedJson()));
}
@SuppressWarnings("rawtypes")
public void testRawCollectionDeserializationNotAlllowed() {
String json = "[0,1,2,3,4,5,6,7,8,9]";
Collection integers = gson.fromJson(json, Collection.class);
Collection<?> integers = gson.fromJson(json, Collection.class);
// JsonReader converts numbers to double by default so we need a floating point comparison
assertEquals(Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0), integers);
json = "[\"Hello\", \"World\"]";
Collection strings = gson.fromJson(json, Collection.class);
Collection<?> strings = gson.fromJson(json, Collection.class);
assertTrue(strings.contains("Hello"));
assertTrue(strings.contains("World"));
}
@SuppressWarnings({"rawtypes", "unchecked"})
public void testRawCollectionOfBagOfPrimitivesNotAllowed() {
BagOfPrimitives bag = new BagOfPrimitives(10, 20, false, "stringValue");
String json = '[' + bag.getExpectedJson() + ',' + bag.getExpectedJson() + ']';
Collection target = gson.fromJson(json, Collection.class);
Collection<?> target = gson.fromJson(json, Collection.class);
assertEquals(2, target.size());
for (Object bag1 : target) {
// Gson 2.0 converts raw objects into maps
@SuppressWarnings("unchecked")
Map<String, Object> values = (Map<String, Object>) bag1;
assertTrue(values.containsValue(10.0));
assertTrue(values.containsValue(20.0));
@ -324,7 +321,7 @@ public class CollectionTest extends TestCase {
HasArrayListField copy = gson.fromJson("{\"longs\":[1,3]}", HasArrayListField.class);
assertEquals(Arrays.asList(1L, 3L), copy.longs);
}
public void testUserCollectionTypeAdapter() {
Type listOfString = new TypeToken<List<String>>() {}.getType();
Object stringListSerializer = new JsonSerializer<List<String>>() {
@ -343,11 +340,10 @@ public class CollectionTest extends TestCase {
ArrayList<Long> longs = new ArrayList<>();
}
@SuppressWarnings("rawtypes")
private static int[] toIntArray(Collection collection) {
private static int[] toIntArray(Collection<?> collection) {
int[] ints = new int[collection.size()];
int i = 0;
for (Iterator iterator = collection.iterator(); iterator.hasNext(); ++i) {
for (Iterator<?> iterator = collection.iterator(); iterator.hasNext(); ++i) {
Object obj = iterator.next();
if (obj instanceof Integer) {
ints[i] = ((Integer)obj).intValue();

View File

@ -29,15 +29,13 @@ import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassWithCustomTypeConverter;
import com.google.gson.reflect.TypeToken;
import java.util.Date;
import junit.framework.TestCase;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import junit.framework.TestCase;
/**
* Functional tests for the support of custom serializer and deserializers.
@ -220,12 +218,11 @@ public class CustomTypeAdaptersTest extends TestCase {
assertEquals("true", gson.toJson(true, Boolean.class));
}
@SuppressWarnings("rawtypes")
public void testCustomDeserializerInvokedForPrimitives() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(boolean.class, new JsonDeserializer() {
.registerTypeAdapter(boolean.class, new JsonDeserializer<Boolean>() {
@Override
public Object deserialize(JsonElement json, Type t, JsonDeserializationContext context) {
public Boolean deserialize(JsonElement json, Type t, JsonDeserializationContext context) {
return json.getAsInt() != 0;
}
})

View File

@ -54,7 +54,6 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
import junit.framework.TestCase;
/**
@ -654,14 +653,13 @@ public class DefaultTypeAdaptersTest extends TestCase {
assertEquals("abc", sb.toString());
}
@SuppressWarnings("rawtypes")
private static class MyClassTypeAdapter extends TypeAdapter<Class> {
private static class MyClassTypeAdapter extends TypeAdapter<Class<?>> {
@Override
public void write(JsonWriter out, Class value) throws IOException {
public void write(JsonWriter out, Class<?> value) throws IOException {
out.value(value.getName());
}
@Override
public Class read(JsonReader in) throws IOException {
public Class<?> read(JsonReader in) throws IOException {
String className = in.nextString();
try {
return Class.forName(className);

View File

@ -22,15 +22,13 @@ import com.google.gson.InstanceCreator;
import com.google.gson.common.TestTypes.Base;
import com.google.gson.common.TestTypes.ClassWithBaseField;
import com.google.gson.common.TestTypes.Sub;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import java.lang.reflect.Type;
import java.util.SortedSet;
import java.util.TreeSet;
import junit.framework.TestCase;
/**
* Functional Test exercising custom serialization only. When test applies to both
@ -93,13 +91,13 @@ public class InstanceCreatorTest extends TestCase {
assertEquals(SubArrayList.class, list.getClass());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings("unchecked")
public void testInstanceCreatorForParametrizedType() throws Exception {
@SuppressWarnings("serial")
class SubTreeSet<T> extends TreeSet<T> {}
InstanceCreator<SortedSet> sortedSetCreator = new InstanceCreator<SortedSet>() {
@Override public SortedSet createInstance(Type type) {
return new SubTreeSet();
InstanceCreator<SortedSet<?>> sortedSetCreator = new InstanceCreator<SortedSet<?>>() {
@Override public SortedSet<?> createInstance(Type type) {
return new SubTreeSet<>();
}
};
Gson gson = new GsonBuilder()

View File

@ -17,7 +17,6 @@
package com.google.gson.functional;
import com.google.gson.Gson;
import junit.framework.TestCase;
/**
@ -34,32 +33,16 @@ public class InternationalizationTest extends TestCase {
gson = new Gson();
}
/*
public void testStringsWithRawChineseCharactersSerialization() throws Exception {
String target = "好好好";
String json = gson.toJson(target);
String expected = "\"\\u597d\\u597d\\u597d\"";
assertEquals(expected, json);
}
*/
public void testStringsWithRawChineseCharactersDeserialization() throws Exception {
String expected = "好好好";
String json = "\"" + expected + "\"";
String actual = gson.fromJson(json, String.class);
assertEquals(expected, actual);
}
public void testStringsWithUnicodeChineseCharactersSerialization() throws Exception {
String target = "\u597d\u597d\u597d";
String json = gson.toJson(target);
String expected = "\"\u597d\u597d\u597d\"";
String expected = '"' + target + '"';
assertEquals(expected, json);
}
public void testStringsWithUnicodeChineseCharactersDeserialization() throws Exception {
String expected = "\u597d\u597d\u597d";
String json = "\"" + expected + "\"";
String json = '"' + expected + '"';
String actual = gson.fromJson(json, String.class);
assertEquals(expected, actual);
}
@ -68,4 +51,25 @@ public class InternationalizationTest extends TestCase {
String actual = gson.fromJson("\"\\u597d\\u597d\\u597d\"", String.class);
assertEquals("\u597d\u597d\u597d", actual);
}
public void testSupplementaryUnicodeSerialization() throws Exception {
// Supplementary code point U+1F60A
String supplementaryCodePoint = new String(new int[] {0x1F60A}, 0, 1);
String json = gson.toJson(supplementaryCodePoint);
assertEquals('"' + supplementaryCodePoint + '"', json);
}
public void testSupplementaryUnicodeDeserialization() throws Exception {
// Supplementary code point U+1F60A
String supplementaryCodePoint = new String(new int[] {0x1F60A}, 0, 1);
String actual = gson.fromJson('"' + supplementaryCodePoint + '"', String.class);
assertEquals(supplementaryCodePoint, actual);
}
public void testSupplementaryUnicodeEscapedDeserialization() throws Exception {
// Supplementary code point U+1F60A
String supplementaryCodePoint = new String(new int[] {0x1F60A}, 0, 1);
String actual = gson.fromJson("\"\\uD83D\\uDE0A\"", String.class);
assertEquals(supplementaryCodePoint, actual);
}
}

View File

@ -161,4 +161,22 @@ public final class JsonAdapterSerializerDeserializerTest extends TestCase {
return new JsonPrimitive("BaseIntegerAdapter");
}
}
public void testJsonAdapterNullSafe() {
Gson gson = new Gson();
String json = gson.toJson(new Computer3(null, null));
assertEquals("{\"user1\":\"UserSerializerDeserializer\"}", json);
Computer3 computer3 = gson.fromJson("{\"user1\":null, \"user2\":null}", Computer3.class);
assertEquals("UserSerializerDeserializer", computer3.user1.name);
assertNull(computer3.user2);
}
private static final class Computer3 {
@JsonAdapter(value = UserSerializerDeserializer.class, nullSafe = false) final User user1;
@JsonAdapter(value = UserSerializerDeserializer.class) final User user2;
Computer3(User user1, User user2) {
this.user1 = user1;
this.user2 = user2;
}
}
}

View File

@ -17,9 +17,8 @@
package com.google.gson.functional;
import com.google.gson.JsonArray;
import junit.framework.TestCase;
import java.math.BigInteger;
import junit.framework.TestCase;
/**
* Functional tests for adding primitives to a JsonArray.

View File

@ -16,18 +16,6 @@
package com.google.gson.functional;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
@ -42,7 +30,17 @@ import com.google.gson.JsonSyntaxException;
import com.google.gson.common.TestTypes;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import junit.framework.TestCase;
/**
@ -78,20 +76,19 @@ public class MapTest extends TestCase {
assertEquals(2, target.get("b").intValue());
}
public void testMapDuplicateKeyDeserialization() {
Gson gsonWithDuplicateKeys = new GsonBuilder()
.enableDuplicateMapKeyDeserialization()
.create();
Type typeOfMap = new TypeToken<Map<String,Integer>>(){}.getType();
String json = "{\"a\":1,\"b\":2,\"b\":3}";
Map<String,Integer> target = gsonWithDuplicateKeys.fromJson(json, typeOfMap);
assertEquals(1, target.get("a").intValue());
assertEquals(3, target.get("b").intValue());
}
public void testMapDuplicateKeyDeserialization() {
Gson gsonWithDuplicateKeys = new GsonBuilder()
.enableDuplicateMapKeyDeserialization()
.create();
Type typeOfMap = new TypeToken<Map<String,Integer>>(){}.getType();
String json = "{\"a\":1,\"b\":2,\"b\":3}";
Map<String,Integer> target = gsonWithDuplicateKeys.fromJson(json, typeOfMap);
assertEquals(1, target.get("a").intValue());
assertEquals(3, target.get("b").intValue());
}
@SuppressWarnings({"unchecked", "rawtypes"})
public void testRawMapSerialization() {
Map map = new LinkedHashMap();
public void testObjectMapSerialization() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", "string");
String json = gson.toJson(map);
@ -658,7 +655,6 @@ public class MapTest extends TestCase {
}
static final class MapWithGeneralMapParameters {
@SuppressWarnings({"rawtypes", "unchecked"})
final Map<String, Object> map = new LinkedHashMap();
final Map<String, Object> map = new LinkedHashMap<>();
}
}

View File

@ -44,7 +44,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import junit.framework.TestCase;
/**
@ -121,18 +120,16 @@ public class ObjectTest extends TestCase {
assertEquals(target.getExpectedJson(), gson.toJson(target));
}
@SuppressWarnings("rawtypes")
public void testClassWithTransientFieldsDeserialization() throws Exception {
String json = "{\"longValue\":[1]}";
ClassWithTransientFields target = gson.fromJson(json, ClassWithTransientFields.class);
ClassWithTransientFields<?> target = gson.fromJson(json, ClassWithTransientFields.class);
assertEquals(json, target.getExpectedJson());
}
@SuppressWarnings("rawtypes")
public void testClassWithTransientFieldsDeserializationTransientFieldsPassedInJsonAreIgnored()
throws Exception {
String json = "{\"transientLongValue\":1,\"longValue\":[1]}";
ClassWithTransientFields target = gson.fromJson(json, ClassWithTransientFields.class);
ClassWithTransientFields<?> target = gson.fromJson(json, ClassWithTransientFields.class);
assertFalse(target.transientLongValue != 1);
}

View File

@ -23,9 +23,6 @@ import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeAdapter;
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeInstanceCreator;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import junit.framework.TestCase;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
@ -35,6 +32,7 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
/**
* Functional tests for the serialization and deserialization of parameterized types in Gson.
@ -154,14 +152,19 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(expected, actual);
}
@SuppressWarnings("unchecked")
@SuppressWarnings("varargs")
@SafeVarargs
private static <T> T[] arrayOf(T... args) {
return args;
}
public void testVariableTypeFieldsAndGenericArraysSerialization() throws Exception {
Integer obj = 0;
Integer[] array = { 1, 2, 3 };
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(5);
List<Integer>[] arrayOfLists = new List[] { list, list };
List<Integer>[] arrayOfLists = arrayOf(list, list);
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
ObjectWithTypeVariables<Integer> objToSerialize =
@ -171,14 +174,13 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objToSerialize.getExpectedJson(), json);
}
@SuppressWarnings("unchecked")
public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Exception {
Integer obj = 0;
Integer[] array = { 1, 2, 3 };
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(5);
List<Integer>[] arrayOfLists = new List[] { list, list };
List<Integer>[] arrayOfLists = arrayOf(list, list);
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
ObjectWithTypeVariables<Integer> objToSerialize =
@ -225,12 +227,11 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}
@SuppressWarnings("unchecked")
public void testParameterizedTypeGenericArraysSerialization() throws Exception {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
List<Integer>[] arrayOfLists = new List[] { list, list };
List<Integer>[] arrayOfLists = arrayOf(list, list);
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
ObjectWithTypeVariables<Integer> objToSerialize =
@ -239,12 +240,11 @@ public class ParameterizedTypesTest extends TestCase {
assertEquals("{\"arrayOfListOfTypeParameters\":[[1,2],[1,2]]}", json);
}
@SuppressWarnings("unchecked")
public void testParameterizedTypeGenericArraysDeserialization() throws Exception {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
List<Integer>[] arrayOfLists = new List[] { list, list };
List<Integer>[] arrayOfLists = arrayOf(list, list);
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
ObjectWithTypeVariables<Integer> objToSerialize =
@ -459,7 +459,7 @@ public class ParameterizedTypesTest extends TestCase {
return true;
}
}
// Begin: tests to reproduce issue 103
private static class Quantity {
@SuppressWarnings("unused")
@ -475,21 +475,21 @@ public class ParameterizedTypesTest extends TestCase {
}
private interface Immutable {
}
public static final class Amount<Q extends Quantity>
public static final class Amount<Q extends Quantity>
implements Measurable<Q>, Field<Amount<?>>, Serializable, Immutable {
private static final long serialVersionUID = -7560491093120970437L;
int value = 30;
}
public void testDeepParameterizedTypeSerialization() {
Amount<MyQuantity> amount = new Amount<>();
String json = gson.toJson(amount);
assertTrue(json.contains("value"));
assertTrue(json.contains("30"));
}
public void testDeepParameterizedTypeDeserialization() {
String json = "{value:30}";
Type type = new TypeToken<Amount<MyQuantity>>() {}.getType();

View File

@ -631,7 +631,7 @@ public class PrimitiveTest extends TestCase {
try {
new Gson().fromJson(value, String.class);
fail();
} catch (JsonIOException expected) { }
} catch (JsonSyntaxException expected) { }
}
public void testHtmlCharacterSerialization() throws Exception {

View File

@ -23,11 +23,9 @@ import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.common.TestTypes.ClassWithTransientFields;
import com.google.gson.common.TestTypes.Nested;
import com.google.gson.common.TestTypes.PrimitiveArray;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
/**
* Functional tests for print formatting.
@ -45,13 +43,12 @@ public class PrintFormattingTest extends TestCase {
gson = new Gson();
}
@SuppressWarnings({"unchecked", "rawtypes"})
public void testCompactFormattingLeavesNoWhiteSpace() {
List list = new ArrayList();
List<Object> list = new ArrayList<>();
list.add(new BagOfPrimitives());
list.add(new Nested());
list.add(new PrimitiveArray());
list.add(new ClassWithTransientFields());
list.add(new ClassWithTransientFields<>());
String json = gson.toJson(list);
assertContainsNoWhiteSpace(json);

View File

@ -20,11 +20,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonStreamParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import java.util.Map;
import junit.framework.TestCase;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
@ -32,6 +28,9 @@ import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.Map;
import junit.framework.TestCase;
/**
* Functional tests for the support of {@link Reader}s and {@link Writer}s.
@ -89,8 +88,8 @@ public class ReadersWritersTest extends TestCase {
}
public void testReadWriteTwoStrings() throws IOException {
Gson gson= new Gson();
CharArrayWriter writer= new CharArrayWriter();
Gson gson = new Gson();
CharArrayWriter writer = new CharArrayWriter();
writer.write(gson.toJson("one").toCharArray());
writer.write(gson.toJson("two").toCharArray());
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
@ -102,8 +101,8 @@ public class ReadersWritersTest extends TestCase {
}
public void testReadWriteTwoObjects() throws IOException {
Gson gson= new Gson();
CharArrayWriter writer= new CharArrayWriter();
Gson gson = new Gson();
CharArrayWriter writer = new CharArrayWriter();
BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one");
writer.write(gson.toJson(expectedOne).toCharArray());
BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two");
@ -132,4 +131,50 @@ public class ReadersWritersTest extends TestCase {
} catch (JsonSyntaxException expected) {
}
}
/**
* Verifies that passing an {@link Appendable} which is not an instance of {@link Writer}
* to {@code Gson.toJson} works correctly.
*/
public void testToJsonAppendable() {
class CustomAppendable implements Appendable {
final StringBuilder stringBuilder = new StringBuilder();
int toStringCallCount = 0;
@Override
public Appendable append(char c) throws IOException {
stringBuilder.append(c);
return this;
}
@Override
public Appendable append(CharSequence csq) throws IOException {
if (csq == null) {
csq = "null"; // Requirement by Writer.append
}
append(csq, 0, csq.length());
return this;
}
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
if (csq == null) {
csq = "null"; // Requirement by Writer.append
}
// According to doc, toString() must return string representation
String s = csq.toString();
toStringCallCount++;
stringBuilder.append(s, start, end);
return this;
}
}
CustomAppendable appendable = new CustomAppendable();
gson.toJson(Arrays.asList("test", 123, true), appendable);
// Make sure CharSequence.toString() was called at least two times to verify that
// CurrentWrite.cachedString is properly overwritten when char array changes
assertTrue(appendable.toStringCallCount >= 2);
assertEquals("[\"test\",123,true]", appendable.stringBuilder.toString());
}
}

View File

@ -8,6 +8,7 @@ import static org.junit.Assert.fail;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
@ -111,8 +112,10 @@ public class ReflectionAccessTest {
// But deserialization should fail
Class<?> internalClass = Collections.emptyList().getClass();
try {
gson.fromJson("{}", internalClass);
gson.fromJson("[]", internalClass);
fail("Missing exception; test has to be run with `--illegal-access=deny`");
} catch (JsonSyntaxException e) {
fail("Unexpected exception; test has to be run with `--illegal-access=deny`");
} catch (JsonIOException expected) {
assertTrue(expected.getMessage().startsWith(
"Failed making constructor 'java.util.Collections$EmptyList#EmptyList()' accessible; "

View File

@ -16,14 +16,6 @@
package com.google.gson.functional;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
@ -34,6 +26,12 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
/**
* Collection of functional tests for DOM tree based type adapters.
@ -44,7 +42,7 @@ public class TreeTypeAdaptersTest extends TestCase {
private static final Student STUDENT1 = new Student(STUDENT1_ID, "first");
private static final Student STUDENT2 = new Student(STUDENT2_ID, "second");
private static final Type TYPE_COURSE_HISTORY =
new TypeToken<Course<HistoryCourse>>(){}.getType();
new TypeToken<Course<HistoryCourse>>(){}.getType();
private static final Id<Course<HistoryCourse>> COURSE_ID =
new Id<>("10", TYPE_COURSE_HISTORY);
@ -94,7 +92,6 @@ public class TreeTypeAdaptersTest extends TestCase {
private static final class IdTreeTypeAdapter implements JsonSerializer<Id<?>>,
JsonDeserializer<Id<?>> {
@SuppressWarnings("rawtypes")
@Override
public Id<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
@ -105,7 +102,7 @@ public class TreeTypeAdaptersTest extends TestCase {
// Since Id takes only one TypeVariable, the actual type corresponding to the first
// TypeVariable is the Type we are looking for
Type typeOfId = parameterizedType.getActualTypeArguments()[0];
return new Id(json.getAsString(), typeOfId);
return new Id<>(json.getAsString(), typeOfId);
}
@Override

View File

@ -16,16 +16,14 @@
package com.google.gson.functional;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Arrays;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
/**
* Functional test for Gson serialization and deserialization of
@ -70,6 +68,7 @@ public class TypeVariableTest extends TestCase {
assertEquals(blue1, blue2);
}
@SuppressWarnings("overrides") // for missing hashCode() override
public static class Blue extends Red<Boolean> {
public Blue() {
super(false);
@ -79,7 +78,6 @@ public class TypeVariableTest extends TestCase {
super(value);
}
// Technically, we should implement hashcode too
@Override
public boolean equals(Object o) {
if (!(o instanceof Blue)) {
@ -100,6 +98,7 @@ public class TypeVariableTest extends TestCase {
}
}
@SuppressWarnings("overrides") // for missing hashCode() override
public static class Foo<S, T> extends Red<Boolean> {
private S someSField;
private T someTField;
@ -113,7 +112,6 @@ public class TypeVariableTest extends TestCase {
this.someTField = tValue;
}
// Technically, we should implement hashcode too
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o) {

View File

@ -15,7 +15,8 @@
*/
package com.google.gson.internal;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
@ -29,7 +30,7 @@ public class JavaVersionTest {
@Test
public void testGetMajorJavaVersion() {
JavaVersion.getMajorJavaVersion();
assertTrue(JavaVersion.getMajorJavaVersion() >= 7); // Gson currently requires at least Java 7
}
@Test

View File

@ -16,6 +16,7 @@
package com.google.gson.internal;
import com.google.gson.common.MoreAsserts;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -26,12 +27,10 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import junit.framework.TestCase;
import com.google.gson.common.MoreAsserts;
public final class LinkedTreeMapTest extends TestCase {
public void testIterationOrder() {
@ -73,6 +72,59 @@ public final class LinkedTreeMapTest extends TestCase {
} catch (ClassCastException expected) {}
}
public void testPutNullValue() {
LinkedTreeMap<String, String> map = new LinkedTreeMap<>();
map.put("a", null);
assertEquals(1, map.size());
assertTrue(map.containsKey("a"));
assertTrue(map.containsValue(null));
assertNull(map.get("a"));
}
public void testPutNullValue_Forbidden() {
LinkedTreeMap<String, String> map = new LinkedTreeMap<>(false);
try {
map.put("a", null);
fail();
} catch (NullPointerException e) {
assertEquals("value == null", e.getMessage());
}
assertEquals(0, map.size());
assertFalse(map.containsKey("a"));
assertFalse(map.containsValue(null));
}
public void testEntrySetValueNull() {
LinkedTreeMap<String, String> map = new LinkedTreeMap<>();
map.put("a", "1");
assertEquals("1", map.get("a"));
Entry<String, String> entry = map.entrySet().iterator().next();
assertEquals("a", entry.getKey());
assertEquals("1", entry.getValue());
entry.setValue(null);
assertNull(entry.getValue());
assertTrue(map.containsKey("a"));
assertTrue(map.containsValue(null));
assertNull(map.get("a"));
}
public void testEntrySetValueNull_Forbidden() {
LinkedTreeMap<String, String> map = new LinkedTreeMap<>(false);
map.put("a", "1");
Entry<String, String> entry = map.entrySet().iterator().next();
try {
entry.setValue(null);
fail();
} catch (NullPointerException e) {
assertEquals("value == null", e.getMessage());
}
assertEquals("1", entry.getValue());
assertEquals("1", map.get("a"));
assertFalse(map.containsValue(null));
}
public void testContainsNonComparableKeyReturnsFalse() {
LinkedTreeMap<String, String> map = new LinkedTreeMap<>();
map.put("a", "android");
@ -81,6 +133,7 @@ public final class LinkedTreeMapTest extends TestCase {
public void testContainsNullKeyIsAlwaysFalse() {
LinkedTreeMap<String, String> map = new LinkedTreeMap<>();
assertFalse(map.containsKey(null));
map.put("a", "android");
assertFalse(map.containsKey(null));
}
@ -160,6 +213,7 @@ public final class LinkedTreeMapTest extends TestCase {
assertEquals(Collections.singletonMap("a", 1), deserialized);
}
@SuppressWarnings("varargs")
@SafeVarargs
private final <T> void assertIterationOrder(Iterable<T> actual, T... expected) {
ArrayList<T> actualList = new ArrayList<>();

View File

@ -0,0 +1,68 @@
package com.google.gson.internal;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.Writer;
import org.junit.Test;
public class StreamsTest {
@Test
public void testWriterForAppendable() throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Writer writer = Streams.writerForAppendable(stringBuilder);
writer.append('a');
writer.append('\u1234');
writer.append("test");
writer.append(null); // test custom null handling mandated by `append`
writer.append("abcdef", 2, 4);
writer.append(null, 1, 3); // test custom null handling mandated by `append`
writer.append(',');
writer.write('a');
writer.write('\u1234');
// Should only consider the 16 low-order bits
writer.write(0x4321_1234);
writer.append(',');
writer.write("chars".toCharArray());
try {
writer.write((char[]) null);
fail();
} catch (NullPointerException e) {
}
writer.write("chars".toCharArray(), 1, 2);
try {
writer.write((char[]) null, 1, 2);
fail();
} catch (NullPointerException e) {
}
writer.append(',');
writer.write("string");
try {
writer.write((String) null);
fail();
} catch (NullPointerException e) {
}
writer.write("string", 1, 2);
try {
writer.write((String) null, 1, 2);
fail();
} catch (NullPointerException e) {
}
String actualOutput = stringBuilder.toString();
assertEquals("a\u1234testnullcdul,a\u1234\u1234,charsha,stringtr", actualOutput);
writer.flush();
writer.close();
// flush() and close() calls should have had no effect
assertEquals(actualOutput, stringBuilder.toString());
}
}

View File

@ -20,6 +20,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import junit.framework.TestCase;
@ -55,19 +56,22 @@ public final class JsonElementReaderTest extends TestCase {
try {
reader.nextDouble();
fail();
} catch (NumberFormatException e) {
} catch (MalformedJsonException e) {
assertEquals("JSON forbids NaN and infinities: NaN", e.getMessage());
}
assertEquals("NaN", reader.nextString());
try {
reader.nextDouble();
fail();
} catch (NumberFormatException e) {
} catch (MalformedJsonException e) {
assertEquals("JSON forbids NaN and infinities: -Infinity", e.getMessage());
}
assertEquals("-Infinity", reader.nextString());
try {
reader.nextDouble();
fail();
} catch (NumberFormatException e) {
} catch (MalformedJsonException e) {
assertEquals("JSON forbids NaN and infinities: Infinity", e.getMessage());
}
assertEquals("Infinity", reader.nextString());
reader.endArray();

View File

@ -16,10 +16,17 @@
package com.google.gson.internal.bind;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.common.MoreAsserts;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
@SuppressWarnings("resource")
@ -54,4 +61,38 @@ public class JsonTreeReaderTest extends TestCase {
reader.endObject();
assertFalse(reader.hasNext());
}
public void testCustomJsonElementSubclass() throws IOException {
@SuppressWarnings("deprecation") // superclass constructor
class CustomSubclass extends JsonElement {
@Override
public JsonElement deepCopy() {
return this;
}
}
JsonArray array = new JsonArray();
array.add(new CustomSubclass());
JsonTreeReader reader = new JsonTreeReader(array);
reader.beginArray();
try {
// Should fail due to custom JsonElement subclass
reader.peek();
fail();
} catch (MalformedJsonException expected) {
assertEquals("Custom JsonElement subclass " + CustomSubclass.class.getName() + " is not supported",
expected.getMessage());
}
}
/**
* {@link JsonTreeReader} effectively replaces the complete reading logic of {@link JsonReader} to
* read from a {@link JsonElement} instead of a {@link Reader}. Therefore all relevant methods of
* {@code JsonReader} must be overridden.
*/
public void testOverrides() {
List<String> ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()");
MoreAsserts.assertOverridesMethods(JsonReader.class, JsonTreeReader.class, ignoredMethods);
}
}

View File

@ -16,8 +16,14 @@
package com.google.gson.internal.bind;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.common.MoreAsserts;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
@SuppressWarnings("resource")
@ -152,17 +158,35 @@ public final class JsonTreeWriterTest extends TestCase {
JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(true);
writer.beginArray();
writer.value(Float.NaN);
writer.value(Float.NEGATIVE_INFINITY);
writer.value(Float.POSITIVE_INFINITY);
writer.value(Double.NaN);
writer.value(Double.NEGATIVE_INFINITY);
writer.value(Double.POSITIVE_INFINITY);
writer.endArray();
assertEquals("[NaN,-Infinity,Infinity]", writer.get().toString());
assertEquals("[NaN,-Infinity,Infinity,NaN,-Infinity,Infinity]", writer.get().toString());
}
public void testStrictNansAndInfinities() throws IOException {
JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(false);
writer.beginArray();
try {
writer.value(Float.NaN);
fail();
} catch (IllegalArgumentException expected) {
}
try {
writer.value(Float.NEGATIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
}
try {
writer.value(Float.POSITIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
}
try {
writer.value(Double.NaN);
fail();
@ -184,6 +208,21 @@ public final class JsonTreeWriterTest extends TestCase {
JsonTreeWriter writer = new JsonTreeWriter();
writer.setLenient(false);
writer.beginArray();
try {
writer.value(Float.valueOf(Float.NaN));
fail();
} catch (IllegalArgumentException expected) {
}
try {
writer.value(Float.valueOf(Float.NEGATIVE_INFINITY));
fail();
} catch (IllegalArgumentException expected) {
}
try {
writer.value(Float.valueOf(Float.POSITIVE_INFINITY));
fail();
} catch (IllegalArgumentException expected) {
}
try {
writer.value(Double.valueOf(Double.NaN));
fail();
@ -200,4 +239,25 @@ public final class JsonTreeWriterTest extends TestCase {
} catch (IllegalArgumentException expected) {
}
}
public void testJsonValue() throws IOException {
JsonTreeWriter writer = new JsonTreeWriter();
writer.beginArray();
try {
writer.jsonValue("test");
fail();
} catch (UnsupportedOperationException expected) {
}
}
/**
* {@link JsonTreeWriter} effectively replaces the complete writing logic of {@link JsonWriter} to
* create a {@link JsonElement} tree instead of writing to a {@link Writer}. Therefore all relevant
* methods of {@code JsonWriter} must be overridden.
*/
public void testOverrides() {
List<String> ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()", "setIndent(java.lang.String)",
"setHtmlSafe(boolean)", "isHtmlSafe()", "setSerializeNulls(boolean)", "getSerializeNulls()", "setOmitQuotes(boolean)", "getOmitQuotes()");
MoreAsserts.assertOverridesMethods(JsonWriter.class, JsonTreeWriter.class, ignoredMethods);
}
}

View File

@ -1,13 +1,18 @@
package com.google.gson.internal.bind.util;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
public class ISO8601UtilsTest {
@ -61,6 +66,26 @@ public class ISO8601UtilsTest {
assertEquals(expectedDate, date);
}
@Test
public void testDateParseInvalidDay() {
String dateStr = "2022-12-33";
try {
ISO8601Utils.parse(dateStr, new ParsePosition(0));
fail("Expected parsing to fail");
} catch (ParseException expected) {
}
}
@Test
public void testDateParseInvalidMonth() {
String dateStr = "2022-14-30";
try {
ISO8601Utils.parse(dateStr, new ParsePosition(0));
fail("Expected parsing to fail");
} catch (ParseException expected) {
}
}
@Test
public void testDateParseWithTimezone() throws ParseException {
String dateStr = "2018-06-25T00:00:00-03:00";

View File

@ -16,6 +16,7 @@
package com.google.gson.reflect;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
@ -91,6 +92,12 @@ public final class TypeTokenTest extends TestCase {
TypeToken<?> expectedListOfStringArray = new TypeToken<List<String>[]>() {};
Type listOfString = new TypeToken<List<String>>() {}.getType();
assertEquals(expectedListOfStringArray, TypeToken.getArray(listOfString));
try {
TypeToken.getArray(null);
fail();
} catch (NullPointerException e) {
}
}
public void testParameterizedFactory() {
@ -104,6 +111,97 @@ public final class TypeTokenTest extends TestCase {
Type listOfString = TypeToken.getParameterized(List.class, String.class).getType();
Type listOfListOfString = TypeToken.getParameterized(List.class, listOfString).getType();
assertEquals(expectedListOfListOfListOfString, TypeToken.getParameterized(List.class, listOfListOfString));
TypeToken<?> expectedWithExactArg = new TypeToken<GenericWithBound<Number>>() {};
assertEquals(expectedWithExactArg, TypeToken.getParameterized(GenericWithBound.class, Number.class));
TypeToken<?> expectedWithSubclassArg = new TypeToken<GenericWithBound<Integer>>() {};
assertEquals(expectedWithSubclassArg, TypeToken.getParameterized(GenericWithBound.class, Integer.class));
TypeToken<?> expectedSatisfyingTwoBounds = new TypeToken<GenericWithMultiBound<ClassSatisfyingBounds>>() {};
assertEquals(expectedSatisfyingTwoBounds, TypeToken.getParameterized(GenericWithMultiBound.class, ClassSatisfyingBounds.class));
}
public void testParameterizedFactory_Invalid() {
try {
TypeToken.getParameterized(null, new Type[0]);
fail();
} catch (NullPointerException e) {
}
GenericArrayType arrayType = (GenericArrayType) TypeToken.getArray(String.class).getType();
try {
TypeToken.getParameterized(arrayType, new Type[0]);
fail();
} catch (IllegalArgumentException e) {
assertEquals("rawType must be of type Class, but was java.lang.String[]", e.getMessage());
}
try {
TypeToken.getParameterized(String.class, String.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("java.lang.String requires 0 type arguments, but got 1", e.getMessage());
}
try {
TypeToken.getParameterized(List.class, new Type[0]);
fail();
} catch (IllegalArgumentException e) {
assertEquals("java.util.List requires 1 type arguments, but got 0", e.getMessage());
}
try {
TypeToken.getParameterized(List.class, String.class, String.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("java.util.List requires 1 type arguments, but got 2", e.getMessage());
}
try {
TypeToken.getParameterized(GenericWithBound.class, String.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Type argument class java.lang.String does not satisfy bounds "
+ "for type variable T declared by " + GenericWithBound.class,
e.getMessage());
}
try {
TypeToken.getParameterized(GenericWithBound.class, Object.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Type argument class java.lang.Object does not satisfy bounds "
+ "for type variable T declared by " + GenericWithBound.class,
e.getMessage());
}
try {
TypeToken.getParameterized(GenericWithMultiBound.class, Number.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Type argument class java.lang.Number does not satisfy bounds "
+ "for type variable T declared by " + GenericWithMultiBound.class,
e.getMessage());
}
try {
TypeToken.getParameterized(GenericWithMultiBound.class, CharSequence.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Type argument interface java.lang.CharSequence does not satisfy bounds "
+ "for type variable T declared by " + GenericWithMultiBound.class,
e.getMessage());
}
try {
TypeToken.getParameterized(GenericWithMultiBound.class, Object.class);
fail();
} catch (IllegalArgumentException e) {
assertEquals("Type argument class java.lang.Object does not satisfy bounds "
+ "for type variable T declared by " + GenericWithMultiBound.class,
e.getMessage());
}
}
private static class CustomTypeToken extends TypeToken<String> {
@ -158,3 +256,13 @@ public final class TypeTokenTest extends TestCase {
}
}
}
// Have to declare these classes here as top-level classes because otherwise tests for
// TypeToken.getParameterized fail due to owner type mismatch
class GenericWithBound<T extends Number> {
}
class GenericWithMultiBound<T extends Number & CharSequence> {
}
@SuppressWarnings("serial")
abstract class ClassSatisfyingBounds extends Number implements CharSequence {
}

View File

@ -172,6 +172,30 @@ public final class JsonWriterTest extends TestCase {
assertEquals("{\"a\":{\"b\":true},\"c\":1}", stringWriter.toString());
}
public void testNonFiniteFloats() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray();
try {
jsonWriter.value(Float.NaN);
fail();
} catch (IllegalArgumentException expected) {
assertEquals("Numeric values must be finite, but was NaN", expected.getMessage());
}
try {
jsonWriter.value(Float.NEGATIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
assertEquals("Numeric values must be finite, but was -Infinity", expected.getMessage());
}
try {
jsonWriter.value(Float.POSITIVE_INFINITY);
fail();
} catch (IllegalArgumentException expected) {
assertEquals("Numeric values must be finite, but was Infinity", expected.getMessage());
}
}
public void testNonFiniteDoubles() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
@ -226,6 +250,18 @@ public final class JsonWriterTest extends TestCase {
}
}
public void testNonFiniteFloatsWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setLenient(true);
jsonWriter.beginArray();
jsonWriter.value(Float.NaN);
jsonWriter.value(Float.NEGATIVE_INFINITY);
jsonWriter.value(Float.POSITIVE_INFINITY);
jsonWriter.endArray();
assertEquals("[NaN,-Infinity,Infinity]", stringWriter.toString());
}
public void testNonFiniteDoublesWhenLenient() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
@ -251,6 +287,36 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[NaN,-Infinity,Infinity,Infinity]", stringWriter.toString());
}
public void testFloats() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray();
jsonWriter.value(-0.0f);
jsonWriter.value(1.0f);
jsonWriter.value(Float.MAX_VALUE);
jsonWriter.value(Float.MIN_VALUE);
jsonWriter.value(0.0f);
jsonWriter.value(-0.5f);
jsonWriter.value(2.2250739E-38f);
jsonWriter.value(3.723379f);
jsonWriter.value((float) Math.PI);
jsonWriter.value((float) Math.E);
jsonWriter.endArray();
jsonWriter.close();
assertEquals(
"[-0.0,"
+ "1.0,"
+ "3.4028235E38,"
+ "1.4E-45,"
+ "0.0,"
+ "-0.5,"
+ "2.2250739E-38,"
+ "3.723379,"
+ "3.1415927,"
+ "2.7182817]",
stringWriter.toString());
}
public void testDoubles() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);

View File

@ -3,7 +3,7 @@
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.9.1-SNAPSHOT</version>
<version>2.9.2-SNAPSHOT</version>
</parent>
<artifactId>gson-metrics</artifactId>
@ -32,7 +32,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.2.2</version>
<version>2.13.4</version>
</dependency>
<dependency>
<groupId>com.google.caliper</groupId>
@ -44,10 +44,18 @@
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<version>0.16.0</version>
<configuration>
<!-- This module is not supposed to be consumed as library, so no need to check API -->
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M2</version>
<configuration>
<!-- Not deployed -->
<skip>true</skip>

22
pom.xml
View File

@ -3,15 +3,9 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.9.1-SNAPSHOT</version>
<version>2.9.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Gson Parent</name>
@ -20,7 +14,7 @@
<modules>
<module>gson</module>
<module>extras</module>
<module>extras</module>
<module>metrics</module>
<module>proto</module>
</modules>
@ -69,6 +63,14 @@
<version>3.10.1</version>
<configuration>
<release>${javaVersion}</release>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
<failOnWarning>true</failOnWarning>
<compilerArgs>
<!-- Enable all warnings, except for ones which cause issues when building with newer JDKs, see also
https://docs.oracle.com/en/java/javase/11/tools/javac.html -->
<compilerArg>-Xlint:all,-options</compilerArg>
</compilerArgs>
<jdkToolchain>
<version>[11,)</version>
</jdkToolchain>
@ -77,7 +79,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
<version>3.4.1</version>
<configuration>
<jdkToolchain>
<version>[11,)</version>
@ -96,6 +98,8 @@
the project URL (= Gson GitHub repo) which is incorrect because it is not
hosting the Javadoc (3) It might fail due to https://bugs.openjdk.java.net/browse/JDK-8212233 -->
<detectOfflineLinks>false</detectOfflineLinks>
<!-- Only show warnings and errors -->
<quiet>true</quiet>
</configuration>
</plugin>
<plugin>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>io.gitlab.jfronny</groupId>
<artifactId>gson-parent</artifactId>
<version>2.9.1-SNAPSHOT</version>
<version>2.9.2-SNAPSHOT</version>
</parent>
<artifactId>proto</artifactId>
@ -87,7 +87,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M2</version>
<configuration>
<!-- Not deployed -->
<skip>true</skip>

View File

@ -16,7 +16,7 @@
package com.google.gson.protobuf;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Objects.requireNonNull;
import com.google.common.base.CaseFormat;
import com.google.common.collect.MapMaker;
@ -104,7 +104,7 @@ public class ProtoTypeAdapter
}
public Builder setEnumSerialization(EnumSerialization enumSerialization) {
this.enumSerialization = checkNotNull(enumSerialization);
this.enumSerialization = requireNonNull(enumSerialization);
return this;
}
@ -144,7 +144,7 @@ public class ProtoTypeAdapter
*/
public Builder addSerializedNameExtension(
Extension<FieldOptions, String> serializedNameExtension) {
serializedNameExtensions.add(checkNotNull(serializedNameExtension));
serializedNameExtensions.add(requireNonNull(serializedNameExtension));
return this;
}
@ -169,7 +169,7 @@ public class ProtoTypeAdapter
*/
public Builder addSerializedEnumValueExtension(
Extension<EnumValueOptions, String> serializedEnumValueExtension) {
serializedEnumValueExtensions.add(checkNotNull(serializedEnumValueExtension));
serializedEnumValueExtensions.add(requireNonNull(serializedEnumValueExtension));
return this;
}