diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index b8a47ca221..0000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -gson/docs/javadocs/* linguist-documentation diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..483fd2effe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,49 @@ +--- +name: Bug report +about: Report a Gson bug. +title: '' +labels: bug +assignees: '' + +--- + +# Gson version + + + +# Java / Android version + + + +# Used tools + +- [ ] Maven; version: +- [ ] Gradle; version: +- [ ] ProGuard (attach the configuration file please); version: +- [ ] ... + +# Description + + + +## Expected behavior + + + +## Actual behavior + + + +# Reproduction steps + + + +1. ... +2. ... + +# Exception stack trace + + +``` + +``` diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..b798788d02 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Usage question + url: https://stackoverflow.com/questions/tagged/gson + about: Ask usage questions on StackOverflow. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..176fa7a150 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Request a feature. ⚠️ Gson is in maintenance mode; large feature requests might be rejected. +title: '' +labels: enhancement +assignees: '' + +--- + +# Problem solved by the feature + + + +# Feature description + + + +# Alternatives / workarounds + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..daec318933 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..0008892ea7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,19 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + 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 diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000000..29b86b329f --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,25 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'gson' + dry-run: false + language: jvm + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'gson' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..17ed734a9c --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,54 @@ +# Based on default config generated by GitHub, see also https://github.com/github/codeql-action + +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + # Run every Monday at 16:10 + - cron: '10 16 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # Run all security queries and maintainability and reliability queries + queries: +security-and-quality + + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Only compile main sources, but ignore test sources because findings for them might not + # be that relevant (though GitHub security view also allows filtering by source type) + # Can replace this with github/codeql-action/autobuild action to run complete build + - name: Compile sources + run: | + mvn compile --batch-mode + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 405a2b3065..0000000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: "Validate Gradle Wrapper" -on: [push, pull_request] - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: gradle/wrapper-validation-action@v1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0c36a2d81c..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: java - -jdk: - - openjdk11 - -install: mvn -f gson install -DskipTests=true -script: mvn -f gson test - -branches: - except: - - gh-pages - -notifications: - email: false - -sudo: false - -cache: - directories: - - $HOME/.m2 diff --git a/CHANGELOG.md b/CHANGELOG.md index f48e126b78..b0790fcd44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,71 @@ 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.** + +* Change target Java version to 7 (#2043) +* Put `module-info.class` into Multi-Release JAR folder (#2013) +* Improve error message when abstract class cannot be constructed (#1814) +* Support EnumMap deserialization (#2071) +* Add LazilyParsedNumber default adapter (#2060) +* Fix JsonReader.hasNext() returning true at end of document (#2061) +* Remove Gradle build support. Build script was outdated and not actively + maintained anymore (#2063) +* Add `GsonBuilder.disableJdkUnsafe()` (#1904) +* Add `UPPER_CASE_WITH_UNDERSCORES` in FieldNamingPolicy (#2024) +* Fix failing to serialize Collection or Map with inaccessible constructor (#1902) +* Improve TreeTypeAdapter thread-safety (#1976) +* Fix `Gson.newJsonWriter` ignoring lenient and HTML-safe setting (#1989) +* Delete unused LinkedHashTreeMap (#1992) +* Make default adapters stricter; improve exception messages (#2000) +* Fix `FieldNamingPolicy.upperCaseFirstLetter` uppercasing non-letter (#2004) + +## Version 2.8.9 + +* Make OSGi bundle's dependency on `sun.misc` optional (#1993). +* Deprecate `Gson.excluder()` exposing internal `Excluder` class (#1986). +* Prevent Java deserialization of internal classes (#1991). +* Improve number strategy implementation (#1987). +* Fix LongSerializationPolicy null handling being inconsistent with Gson (#1990). +* Support arbitrary Number implementation for Object and Number deserialization (#1290). +* Bump proguard-maven-plugin from 2.4.0 to 2.5.1 (#1980). +* Don't exclude static local classes (#1969). +* Fix `RuntimeTypeAdapterFactory` depending on internal `Streams` class (#1959). +* Improve Maven build (#1964). +* Make dependency on `java.sql` optional (#1707). + +## Version 2.8.8 + +* Fixed issue with recursive types (#1390). +* Better behaviour with Java 9+ and `Unsafe` if there is a security manager (#1712). +* `EnumTypeAdapter` now works better when ProGuard has obfuscated enum fields (#1495). + +## Version 2.8.7 + +* Fixed `ISO8601UtilsTest` failing on systems with UTC+X. +* Improved javadoc for `JsonStreamParser`. +* Updated proguard.cfg (#1693). +* Fixed `IllegalStateException` in `JsonTreeWriter` (#1592). +* Added `JsonArray.isEmpty()` (#1640). +* Added new test cases (#1638). +* Fixed OSGi metadata generation to work on JavaSE < 9 (#1603). + ## Version 2.8.6 _2019-10-04_ [GitHub Diff](https://github.com/google/gson/compare/gson-parent-2.8.5...gson-parent-2.8.6) * Added static methods `JsonParser.parseString` and `JsonParser.parseReader` and deprecated instance method `JsonParser.parse` diff --git a/README.md b/README.md index e3d9a9a1a7..0e785781bf 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Gson can work with arbitrary Java objects including pre-existing objects that yo There are a few open-source projects that can convert Java objects to JSON. However, most of them require that you place Java annotations in your classes; something that you can not do if you do not have access to the source-code. Most also do not fully support the use of Java Generics. Gson considers both of these as very important design goals. +:information_source: Gson is currently in maintenance mode; existing bugs will be fixed, but large new features will likely not be added. If you want to add a new feature, please first search for existing GitHub issues, or create a new one to discuss the feature and get feedback. + ### Goals * Provide simple `toJson()` and `fromJson()` methods to convert Java objects to JSON and vice-versa * Allow pre-existing unmodifiable objects to be converted to and from JSON @@ -17,7 +19,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.8.6' + implementation 'com.google.code.gson:gson:2.9.1' } ``` @@ -26,13 +28,32 @@ Maven: com.google.code.gson gson - 2.8.6 + 2.9.1 ``` [Gson jar downloads](https://maven-badges.herokuapp.com/maven-central/com.google.code.gson/gson) are available from Maven Central. -[![Build Status](https://travis-ci.org/google/gson.svg?branch=master)](https://travis-ci.org/google/gson) +![Build Status](https://github.com/google/gson/actions/workflows/build.yml/badge.svg) + +### Requirements +#### Minimum Java version +- Gson 2.9.0 and newer: Java 7 +- Gson 2.8.9 and older: Java 6 + +Despite supporting older Java versions, Gson also provides a JPMS module descriptor (module name `com.google.gson`) for users of Java 9 or newer. + +#### JPMS dependencies (Java 9+) +These are the optional Java Platform Module System (JPMS) JDK modules which Gson depends on. +This only applies when running Java 9 or newer. + +- `java.sql` (optional since Gson 2.8.9) +When this module is present, Gson provides default adapters for some SQL date and time classes. + +- `jdk.unsupported`, respectively class `sun.misc.Unsafe` (optional) +When this module is present, Gson can use the `Unsafe` class to create instances of classes without no-args constructor. +However, care should be taken when relying on this. `Unsafe` is not available in all environments and its usage has some pitfalls, +see [`GsonBuilder.disableJdkUnsafe()`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/GsonBuilder.html#disableJdkUnsafe()). ### Documentation * [API Javadoc](https://www.javadoc.io/doc/com.google.code.gson/gson): Documentation for the current release @@ -40,7 +61,7 @@ Maven: * [Change log](https://github.com/google/gson/blob/master/CHANGELOG.md): Changes in the recent versions * [Design document](https://github.com/google/gson/blob/master/GsonDesignDocument.md): This document discusses issues we faced while designing Gson. It also includes a comparison of Gson with other Java libraries that can be used for Json conversion -Please use the 'gson' tag on StackOverflow or the [google-gson Google group](https://groups.google.com/group/google-gson) to discuss Gson or to post questions. +Please use the ['gson' tag on StackOverflow](https://stackoverflow.com/questions/tagged/gson) or the [google-gson Google group](https://groups.google.com/group/google-gson) to discuss Gson or to post questions. ### Related Content Created by Third Parties * [Gson Tutorial](https://www.studytrails.com/java/json/java-google-json-introduction/) by `StudyTrails` diff --git a/UserGuide.md b/UserGuide.md index ff9f48c398..ec7fb7ebad 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -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. ## Using Gson with Gradle/Android -``` + +```gradle dependencies { - implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.code.gson:gson:2.9.1' } ``` + ## 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 - + com.google.code.gson gson - 2.8.6 + 2.9.1 compile ``` -That is it, now your maven project is Gson enabled. +That is it, now your Maven project is Gson enabled. ### 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"} ``` @@ -160,23 +164,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 { @@ -204,7 +208,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 +218,7 @@ We also support multi-dimensional arrays, with arbitrarily complex element types ```java Gson gson = new Gson(); -Collection ints = Lists.immutableList(1,2,3,4,5); +Collection ints = Arrays.asList(1,2,3,4,5); // Serialization String json = gson.toJson(ints); // ==> json is [1,2,3,4,5] @@ -233,6 +237,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. +### 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 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 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>(){}.getType(); +String json = "{\"key\": \"value\"}"; + +// Deserialization +Map 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 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 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. + ### 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 +321,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`, 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>() {}.getType(); @@ -258,6 +329,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. ### Serializing and Deserializing Collection with Objects of Arbitrary Types @@ -306,7 +378,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 +387,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 @@ -429,7 +501,7 @@ class IdInstanceCreator implements InstanceCreator> { public Id createInstance(Type type) { Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments(); Type idType = typeParameters[0]; // Id has only one parameterized type T - return Id.get((Class)idType, 0L); + return new Id((Class)idType, 0L); } } ``` @@ -443,10 +515,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); ``` + ### 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 +631,7 @@ This feature provides a way where you can mark certain fields of your objects to #### 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 +684,13 @@ public static void main(String[] args) { The output is: -``` +```json {"longField":1234} ``` ### 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 +715,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. ### Sharing State Across Custom Serializers and Deserializers @@ -663,7 +737,7 @@ In addition Gson's object model and data binding, you can use Gson to read from ## 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. ## Future Enhancements to Gson diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 28586b5ec6..0000000000 --- a/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -buildscript { - repositories { - mavenCentral() - } -} - -allprojects { - repositories { - mavenCentral() - } -} - diff --git a/codegen/pom.xml b/codegen/pom.xml deleted file mode 100644 index c2f9499a31..0000000000 --- a/codegen/pom.xml +++ /dev/null @@ -1,182 +0,0 @@ - - 4.0.0 - com.google.code.gson - gson-codegen - jar - 1.0-SNAPSHOT - 2008 - Gson Code Gen - - org.sonatype.oss - oss-parent - 7 - - http://code.google.com/p/google-gson/ - Google Gson grab bag of utilities, type adapters, etc. - - UTF-8 - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - scm:svn:http://google-gson.googlecode.com/svn/trunk/extras - scm:svn:https://google-gson.googlecode.com/svn/trunk/extras - http://google-gson.codegoogle.com/svn/trunk/extras - - - Google Code Issue Tracking - http://code.google.com/p/google-gson/issues/list - - - Google, Inc. - http://www.google.com - - - - junit - junit - 3.8.2 - test - - - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.4 - - - sign-artifacts - verify - - sign - - - - - - - - - - package - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.6 - 1.6 - -proc:none - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - package - - jar - - - - - - false - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - verify - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.8.1 - - - attach-javadocs - - jar - - - - - - http://download.oracle.com/javase/1.5.0/docs/api/ - - true - public - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - true - - ../eclipse-ws/ - - - file:///${basedir}/../lib/gson-formatting-styles.xml - - - - - org.apache.maven.plugins - maven-release-plugin - - - -DenableCiProfile=true - https://google-gson.googlecode.com/svn/tags/ - - - - - - - Inderjeet Singh - Trymph Inc. - - - Joel Leitch - Google Inc. - - - Jesse Wilson - Square Inc. - - - diff --git a/codegen/src/main/java/com/google/gson/codegen/CodeGen.java b/codegen/src/main/java/com/google/gson/codegen/CodeGen.java deleted file mode 100644 index 011568ff92..0000000000 --- a/codegen/src/main/java/com/google/gson/codegen/CodeGen.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.gson.codegen; - -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; - -public class CodeGen { - private CodeGen() { - } - - public static PackageElement getPackage(Element type) { - while (type.getKind() != ElementKind.PACKAGE) { - type = type.getEnclosingElement(); - } - return (PackageElement) type; - } - - /** - * Returns a fully qualified class name to complement {@code type}. - */ - public static String adapterName(TypeElement typeElement, String suffix) { - StringBuilder builder = new StringBuilder(); - rawTypeToString(builder, typeElement, '$'); - builder.append(suffix); - return builder.toString(); - } - - static void rawTypeToString(StringBuilder result, TypeElement type, char innerClassSeparator) { - String packageName = getPackage(type).getQualifiedName().toString(); - String qualifiedName = type.getQualifiedName().toString(); - result.append(packageName); - result.append('.'); - result.append( - qualifiedName.substring(packageName.length() + 1).replace('.', innerClassSeparator)); - } -} diff --git a/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java b/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java deleted file mode 100644 index 1694d88ac2..0000000000 --- a/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapter.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.gson.codegen; - -public @interface GeneratedTypeAdapter { - Class[] value() default {}; -} diff --git a/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java b/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java deleted file mode 100644 index cd542bc353..0000000000 --- a/codegen/src/main/java/com/google/gson/codegen/GeneratedTypeAdapterProcessor.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.gson.codegen; - -import static java.lang.reflect.Modifier.FINAL; - -import java.io.IOException; -import java.util.Set; - -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; -import javax.tools.JavaFileObject; - -@SupportedAnnotationTypes("com.google.gson.codegen.GeneratedTypeAdapter") -@SupportedSourceVersion(SourceVersion.RELEASE_6) -public final class GeneratedTypeAdapterProcessor extends AbstractProcessor { - @Override public boolean process(Set types, RoundEnvironment env) { - System.out.println("invoked GeneratedTypeAdapterProcessor"); - try { - for (Element element : env.getElementsAnnotatedWith(GeneratedTypeAdapter.class)) { - writeAdapter((TypeElement) element); - } - } catch (IOException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); - } - return true; - } - - private void writeAdapter(TypeElement type) throws IOException { - String typeAdapterName = CodeGen.adapterName(type, "$TypeAdapter"); - JavaFileObject sourceFile = processingEnv.getFiler() - .createSourceFile(typeAdapterName, type); - System.out.println("Generating type adapter: " + typeAdapterName + " in " + sourceFile.getName()); - - JavaWriter writer = new JavaWriter(sourceFile.openWriter()); - writer.addPackage(CodeGen.getPackage(type).getQualifiedName().toString()); - writer.beginType(typeAdapterName, "class", FINAL, null); - writer.endType(); - writer.close(); - } -} diff --git a/codegen/src/main/java/com/google/gson/codegen/JavaWriter.java b/codegen/src/main/java/com/google/gson/codegen/JavaWriter.java deleted file mode 100644 index ccba6c8cd0..0000000000 --- a/codegen/src/main/java/com/google/gson/codegen/JavaWriter.java +++ /dev/null @@ -1,443 +0,0 @@ -/** - * Copyright (C) 2012 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.gson.codegen; - -import java.io.IOException; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Emits Java source files. - */ -public final class JavaWriter { - private static final Pattern TYPE_PATTERN = Pattern.compile("(?:[\\w$]+\\.)*([\\w$]+)"); - private static final String INDENT = " "; - - /** Map fully qualified type names to their short names. */ - private final Map importedTypes = new HashMap(); - - private String packagePrefix; - private final List scopes = new ArrayList(); - private final Writer out; - - /** - * @param out the stream to which Java source will be written. This should be - * a buffered stream. - */ - public JavaWriter(Writer out) { - this.out = out; - } - - /** - * Emit a package declaration. - */ - public void addPackage(String packageName) throws IOException { - if (this.packagePrefix != null) { - throw new IllegalStateException(); - } - out.write("package "); - out.write(packageName); - out.write(";\n"); - this.packagePrefix = packageName + "."; - } - - /** - * Equivalent to {@code addImport(type.getName())}. - */ - public void addImport(Class type) throws IOException { - addImport(type.getName()); - } - - /** - * Emit an import for {@code type}. For the duration of the file, all - * references to this class will be automatically shortened. - */ - public void addImport(String type) throws IOException { - Matcher matcher = TYPE_PATTERN.matcher(type); - if (!matcher.matches()) { - throw new IllegalArgumentException(type); - } - if (importedTypes.put(type, matcher.group(1)) != null) { - throw new IllegalArgumentException(type); - } - out.write("import "); - out.write(type); - out.write(";\n"); - } - - /** - * Emits a name like {@code java.lang.String} or {@code - * java.util.List}, shorting it with imports if - * possible. - */ - private void type(String type) throws IOException { - if (this.packagePrefix == null) { - throw new IllegalStateException(); - } - - Matcher m = TYPE_PATTERN.matcher(type); - int pos = 0; - while (true) { - boolean found = m.find(pos); - - // copy non-matching characters like "<" - int typeStart = found ? m.start() : type.length(); - out.write(type, pos, typeStart - pos); - - if (!found) { - break; - } - - // copy a single class name, shortening it if possible - String name = m.group(0); - String imported; - if ((imported = importedTypes.get(name)) != null) { - out.write(imported); - } else if (name.startsWith(packagePrefix) - && name.indexOf('.', packagePrefix.length()) == -1) { - out.write(name.substring(packagePrefix.length())); - } else if (name.startsWith("java.lang.")) { - out.write(name.substring("java.lang.".length())); - } else { - out.write(name); - } - pos = m.end(); - } - } - - /** - * Emits a type declaration. - * - * @param kind such as "class", "interface" or "enum". - */ - public void beginType(String type, String kind, int modifiers) throws IOException { - beginType(type, kind, modifiers, null); - } - - /** - * Emits a type declaration. - * - * @param kind such as "class", "interface" or "enum". - * @param extendsType the class to extend, or null for no extends clause. - */ - public void beginType(String type, String kind, int modifiers, - String extendsType, String... implementsTypes) throws IOException { - indent(); - modifiers(modifiers); - out.write(kind); - out.write(" "); - type(type); - if (extendsType != null) { - out.write("\n"); - indent(); - out.write(" extends "); - type(extendsType); - } - if (implementsTypes.length > 0) { - out.write("\n"); - indent(); - out.write(" implements "); - for (int i = 0; i < implementsTypes.length; i++) { - if (i != 0) { - out.write(", "); - } - type(implementsTypes[i]); - } - } - out.write(" {\n"); - pushScope(Scope.TYPE_DECLARATION); - } - - /** - * Completes the current type declaration. - */ - public void endType() throws IOException { - if (popScope() != Scope.TYPE_DECLARATION) { - throw new IllegalStateException(); - } - indent(); - out.write("}\n"); - } - - /** - * Emits a field declaration. - */ - public void field(String type, String name, int modifiers) throws IOException { - field(type, name, modifiers, null); - } - - public void field(String type, String name, int modifiers, String initialValue) - throws IOException { - indent(); - modifiers(modifiers); - type(type); - out.write(" "); - out.write(name); - - if (initialValue != null) { - out.write(" = "); - out.write(initialValue); - } - out.write(";\n"); - } - - /** - * Emit a method declaration. - * - * @param returnType the method's return type, or null for constructors. - * @param parameters alternating parameter types and names. - * @param name the method name, or the fully qualified class name for - * constructors. - */ - public void beginMethod(String returnType, String name, int modifiers, String... parameters) - throws IOException { - indent(); - modifiers(modifiers); - if (returnType != null) { - type(returnType); - out.write(" "); - out.write(name); - } else { - type(name); - } - out.write("("); - for (int p = 0; p < parameters.length; ) { - if (p != 0) { - out.write(", "); - } - type(parameters[p++]); - out.write(" "); - type(parameters[p++]); - } - out.write(")"); - if ((modifiers & Modifier.ABSTRACT) != 0) { - out.write(";\n"); - pushScope(Scope.ABSTRACT_METHOD); - } else { - out.write(" {\n"); - pushScope(Scope.NON_ABSTRACT_METHOD); - } - } - - /** - * Annotates the next element with {@code annotation}. The annotation has no - * attributes. - */ - public void annotation(String annotation) throws IOException { - indent(); - out.write("@"); - type(annotation); - out.write("\n"); - } - - /** - * Equivalent to {@code annotation(annotationType.getName())}. - */ - public void annotation(Class annotationType) throws IOException { - annotation(annotationType.getName()); - } - - /** - * @param pattern a code pattern like "int i = %s". Shouldn't contain a - * trailing semicolon or newline character. - */ - public void statement(String pattern, Object... args) throws IOException { - checkInMethod(); - indent(); - out.write(String.format(pattern, args)); - out.write(";\n"); - } - - /** - * @param controlFlow the control flow construct and its code, such as - * "if (foo == 5)". Shouldn't contain braces or newline characters. - */ - public void beginControlFlow(String controlFlow) throws IOException { - checkInMethod(); - indent(); - out.write(controlFlow); - out.write(" {\n"); - pushScope(Scope.CONTROL_FLOW); - } - - /** - * @param controlFlow the control flow construct and its code, such as - * "else if (foo == 10)". Shouldn't contain braces or newline characters. - */ - public void nextControlFlow(String controlFlow) throws IOException { - if (popScope() != Scope.CONTROL_FLOW) { - throw new IllegalArgumentException(); - } - - indent(); - pushScope(Scope.CONTROL_FLOW); - out.write("} "); - out.write(controlFlow); - out.write(" {\n"); - } - - public void endControlFlow() throws IOException { - endControlFlow(null); - } - - /** - * @param controlFlow the optional control flow construct and its code, such - * as "while(foo == 20)". Only used for "do/while" control flows. - */ - public void endControlFlow(String controlFlow) throws IOException { - if (popScope() != Scope.CONTROL_FLOW) { - throw new IllegalArgumentException(); - } - - indent(); - if (controlFlow != null) { - out.write("} "); - out.write(controlFlow); - out.write(";\n"); - } else { - out.write("}\n"); - } - } - - /** - * Completes the current method declaration. - */ - public void endMethod() throws IOException { - Scope popped = popScope(); - if (popped == Scope.NON_ABSTRACT_METHOD) { - indent(); - out.write("}\n"); - } else if (popped != Scope.ABSTRACT_METHOD) { - throw new IllegalStateException(); - } - } - - /** - * Returns the string literal representing {@code data}, including wrapping - * quotes. - */ - public static String stringLiteral(String data) { - StringBuilder result = new StringBuilder(); - result.append('"'); - for (int i = 0; i < data.length(); i++) { - char c = data.charAt(i); - switch (c) { - case '"': - result.append("\\\""); - break; - case '\\': - result.append("\\\\"); - break; - case '\t': - result.append("\\\t"); - break; - case '\b': - result.append("\\\b"); - break; - case '\n': - result.append("\\\n"); - break; - case '\r': - result.append("\\\r"); - break; - case '\f': - result.append("\\\f"); - break; - default: - result.append(c); - } - } - result.append('"'); - return result.toString(); - } - - public void close() throws IOException { - out.close(); - } - - /** - * Emit modifier names. - */ - private void modifiers(int modifiers) throws IOException { - if ((modifiers & Modifier.PUBLIC) != 0) { - out.write("public "); - } - if ((modifiers & Modifier.PRIVATE) != 0) { - out.write("private "); - } - if ((modifiers & Modifier.PROTECTED) != 0) { - out.write("protected "); - } - if ((modifiers & Modifier.STATIC) != 0) { - out.write("static "); - } - if ((modifiers & Modifier.FINAL) != 0) { - out.write("final "); - } - if ((modifiers & Modifier.ABSTRACT) != 0) { - out.write("abstract "); - } - if ((modifiers & Modifier.SYNCHRONIZED) != 0) { - out.write("synchronized "); - } - if ((modifiers & Modifier.TRANSIENT) != 0) { - out.write("transient "); - } - if ((modifiers & Modifier.VOLATILE) != 0) { - out.write("volatile "); - } - } - - private void indent() throws IOException { - for (int i = 0; i < scopes.size(); i++) { - out.write(INDENT); - } - } - - private void checkInMethod() { - Scope scope = peekScope(); - if (scope != Scope.NON_ABSTRACT_METHOD && scope != Scope.CONTROL_FLOW) { - throw new IllegalArgumentException(); - } - } - - private void pushScope(Scope pushed) { - scopes.add(pushed); - } - - private Scope peekScope() { - return scopes.get(scopes.size() - 1); - } - - private Scope popScope() { - return scopes.remove(scopes.size() - 1); - } - - private enum Scope { - TYPE_DECLARATION, - ABSTRACT_METHOD, - NON_ABSTRACT_METHOD, - CONTROL_FLOW, - } -} diff --git a/codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index a052da0c14..0000000000 --- a/codegen/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -com.google.gson.codegen.GeneratedTypeAdapterProcessor \ No newline at end of file diff --git a/codegen/src/test/java/com/google/gson/codegen/functional/CodeGenFunctionalTest.java b/codegen/src/test/java/com/google/gson/codegen/functional/CodeGenFunctionalTest.java deleted file mode 100644 index 855ee3faf5..0000000000 --- a/codegen/src/test/java/com/google/gson/codegen/functional/CodeGenFunctionalTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.gson.codegen.functional; - -import junit.framework.TestCase; - -public class CodeGenFunctionalTest extends TestCase { - - public void testGeneratedJson() { - Order order = new Order("toy", 10); - // TODO: figure out how to access the generated type adapter - } -} diff --git a/codegen/src/test/java/com/google/gson/codegen/functional/Order.java b/codegen/src/test/java/com/google/gson/codegen/functional/Order.java deleted file mode 100644 index 916587f3bf..0000000000 --- a/codegen/src/test/java/com/google/gson/codegen/functional/Order.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.gson.codegen.functional; - -import com.google.gson.codegen.GeneratedTypeAdapter; - -@GeneratedTypeAdapter -final class Order { - private final String itemName; - private final int price; - - public Order(String itemName, int price) { - this.itemName = itemName; - this.price = price; - } - - public String getItemName() { - return itemName; - } - - public int getAmount() { - return price; - } -} diff --git a/examples/android-proguard-example/README.md b/examples/android-proguard-example/README.md new file mode 100644 index 0000000000..093c8eebca --- /dev/null +++ b/examples/android-proguard-example/README.md @@ -0,0 +1,12 @@ +# android-proguard-example + +Example Android project showing how to properly configure [ProGuard](https://www.guardsquare.com/proguard). +ProGuard is a tool for 'shrinking' and obfuscating compiled classes. It can rename methods and fields, +or remove them if they appear to be unused. This can cause issues for Gson which uses Java reflection to +access the fields of a class. It is necessary to configure ProGuard to make sure that Gson works correctly. + +Also have a look at the [ProGuard manual](https://www.guardsquare.com/manual/configuration/usage#keepoverview) +for more details on how ProGuard can be configured. + +The R8 code shrinker uses the same rule format as ProGuard, but there are differences between these two +tools. Have a look at R8's Compatibility FAQ, and especially at the [Gson section](https://r8.googlesource.com/r8/+/refs/heads/main/compatibility-faq.md#gson). diff --git a/examples/android-proguard-example/proguard.cfg b/examples/android-proguard-example/proguard.cfg index e0e0a97f48..95f31ec6d6 100644 --- a/examples/android-proguard-example/proguard.cfg +++ b/examples/android-proguard-example/proguard.cfg @@ -25,4 +25,8 @@ @com.google.gson.annotations.SerializedName ; } +# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. +-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken +-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken + ##---------------End: proguard configuration for Gson ---------- diff --git a/examples/android-proguard-example/src/com/google/gson/examples/android/GsonProguardExampleActivity.java b/examples/android-proguard-example/src/com/google/gson/examples/android/GsonProguardExampleActivity.java index bd54431148..3ac740e48d 100644 --- a/examples/android-proguard-example/src/com/google/gson/examples/android/GsonProguardExampleActivity.java +++ b/examples/android-proguard-example/src/com/google/gson/examples/android/GsonProguardExampleActivity.java @@ -53,7 +53,7 @@ public void onCreate(Bundle icicle) { } private Cart buildCart() { - List lineItems = new ArrayList(); + List lineItems = new ArrayList<>(); lineItems.add(new LineItem("hammer", 1, 12000000, "USD")); return new Cart(lineItems, "Happy Buyer", "4111-1111-1111-1111"); } diff --git a/extras/README.md b/extras/README.md new file mode 100644 index 0000000000..41447726e2 --- /dev/null +++ b/extras/README.md @@ -0,0 +1,6 @@ +# extras + +This Maven module contains the source code for supplementary Gson features which +are not included by default. + +The artifacts created by this module are currently not deployed to Maven Central. diff --git a/extras/pom.xml b/extras/pom.xml index 1c5e76c393..586e7b2f72 100644 --- a/extras/pom.xml +++ b/extras/pom.xml @@ -1,47 +1,33 @@ 4.0.0 - com.google.code.gson + + com.google.code.gson + gson-parent + 2.9.2-SNAPSHOT + + gson-extras - jar - 1.0-SNAPSHOT 2008 Gson Extras - - org.sonatype.oss - oss-parent - 9 - - http://code.google.com/p/google-gson/ Google Gson grab bag of utilities, type adapters, etc. - - UTF-8 - + - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt - - scm:svn:http://google-gson.googlecode.com/svn/trunk/extras - scm:svn:https://google-gson.googlecode.com/svn/trunk/extras - http://google-gson.codegoogle.com/svn/trunk/extras - - - Google Code Issue Tracking - http://code.google.com/p/google-gson/issues/list - + Google, Inc. - http://www.google.com + https://www.google.com + com.google.code.gson gson - 2.7 - compile + ${project.parent.version} javax.annotation @@ -51,130 +37,26 @@ junit junit - 3.8.2 test - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - + - package - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-jar-plugin - 3.0.2 - - - package - - jar - - - - - - false - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - verify - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - - attach-javadocs - - jar - - - - - - http://download.oracle.com/javase/1.5.0/docs/api/ - - true - public - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.10 - - true - true - - ../eclipse-ws/ - - - file:///${basedir}/../lib/gson-formatting-styles.xml - - - - - org.apache.maven.plugins - maven-release-plugin - - - -DenableCiProfile=true - https://google-gson.googlecode.com/svn/tags/ - - - + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0 + + + true + + + + + Inderjeet Singh diff --git a/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java b/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java index bd7c2d24d0..9ea350cae8 100644 --- a/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java +++ b/extras/src/main/java/com/google/gson/extras/examples/rawcollections/RawCollectionsExample.java @@ -45,8 +45,7 @@ public static void main(String[] args) { collection.add(new Event("GREETINGS", "guest")); String json = gson.toJson(collection); System.out.println("Using Gson.toJson() on a raw collection: " + json); - JsonParser parser = new JsonParser(); - JsonArray array = parser.parse(json).getAsJsonArray(); + JsonArray array = JsonParser.parseString(json).getAsJsonArray(); String message = gson.fromJson(array.get(0), String.class); int number = gson.fromJson(array.get(1), int.class); Event event = gson.fromJson(array.get(2), Event.class); diff --git a/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java b/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java index cd8ea00f47..e6a07f141d 100644 --- a/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java +++ b/extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java @@ -20,6 +20,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.InstanceCreator; import com.google.gson.JsonElement; +import com.google.gson.ReflectionAccessFilter; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.ConstructorConstructor; @@ -30,6 +31,7 @@ import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.lang.reflect.Type; +import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedList; @@ -46,12 +48,13 @@ public final class GraphAdapterBuilder { private final ConstructorConstructor constructorConstructor; public GraphAdapterBuilder() { - this.instanceCreators = new HashMap>(); - this.constructorConstructor = new ConstructorConstructor(instanceCreators); + this.instanceCreators = new HashMap<>(); + this.constructorConstructor = new ConstructorConstructor(instanceCreators, true, Collections.emptyList()); } public GraphAdapterBuilder addType(Type type) { final ObjectConstructor objectConstructor = constructorConstructor.get(TypeToken.get(type)); InstanceCreator instanceCreator = new InstanceCreator() { + @Override public Object createInstance(Type type) { return objectConstructor.construct(); } @@ -77,12 +80,13 @@ public void registerOn(GsonBuilder gsonBuilder) { static class Factory implements TypeAdapterFactory, InstanceCreator { private final Map> instanceCreators; - private final ThreadLocal graphThreadLocal = new ThreadLocal(); + private final ThreadLocal graphThreadLocal = new ThreadLocal<>(); Factory(Map> instanceCreators) { this.instanceCreators = instanceCreators; } + @Override public TypeAdapter create(Gson gson, TypeToken type) { if (!instanceCreators.containsKey(type.getType())) { return null; @@ -117,7 +121,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { @SuppressWarnings("unchecked") // graph.map guarantees consistency between value and T Element element = (Element) graph.map.get(value); if (element == null) { - element = new Element(value, graph.nextName(), typeAdapter, null); + element = new Element<>(value, graph.nextName(), typeAdapter, null); graph.map.put(value, element); graph.queue.add(element); } @@ -174,7 +178,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { currentName = name; } JsonElement element = elementAdapter.read(in); - graph.map.put(name, new Element(null, name, typeAdapter, element)); + graph.map.put(name, new Element<>(null, name, typeAdapter, element)); } in.endObject(); } else { @@ -212,6 +216,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { * 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(); if (graph == null || graph.nextCreate == null) { @@ -237,7 +242,7 @@ static class Graph { * The queue of elements to write during serialization. Unused during * deserialization. */ - private final Queue queue = new LinkedList(); + private final Queue queue = new LinkedList<>(); /** * The instance currently being deserialized. Used as a backdoor between diff --git a/extras/src/main/java/com/google/gson/interceptors/Intercept.java b/extras/src/main/java/com/google/gson/interceptors/Intercept.java index 0c4e9043f6..fef29cbf0b 100644 --- a/extras/src/main/java/com/google/gson/interceptors/Intercept.java +++ b/extras/src/main/java/com/google/gson/interceptors/Intercept.java @@ -28,8 +28,8 @@ * after it has been deserialized from Json. * Here is an example of how this annotation is used: *

Here is an example of how this annotation is used: - *

- * @Intercept(postDeserialize=UserValidator.class)
+ * 
+ * @Intercept(postDeserialize=UserValidator.class)
  * public class User {
  *   String name;
  *   String password;
@@ -47,7 +47,7 @@
  *     }
  *   }
  * }
- * 

+ *
* * @author Inderjeet Singh */ diff --git a/extras/src/main/java/com/google/gson/interceptors/InterceptorFactory.java b/extras/src/main/java/com/google/gson/interceptors/InterceptorFactory.java index 907fca3a42..51916c220d 100644 --- a/extras/src/main/java/com/google/gson/interceptors/InterceptorFactory.java +++ b/extras/src/main/java/com/google/gson/interceptors/InterceptorFactory.java @@ -12,14 +12,14 @@ * A type adapter factory that implements {@code @Intercept}. */ public final class InterceptorFactory implements TypeAdapterFactory { - public TypeAdapter create(Gson gson, TypeToken type) { + @Override public TypeAdapter create(Gson gson, TypeToken type) { Intercept intercept = type.getRawType().getAnnotation(Intercept.class); if (intercept == null) { return null; } TypeAdapter delegate = gson.getDelegateAdapter(this, type); - return new InterceptorAdapter(delegate, intercept); + return new InterceptorAdapter<>(delegate, intercept); } static class InterceptorAdapter extends TypeAdapter { diff --git a/extras/src/main/java/com/google/gson/typeadapters/PostConstructAdapterFactory.java b/extras/src/main/java/com/google/gson/typeadapters/PostConstructAdapterFactory.java index 36e06da20f..450ebbab2d 100644 --- a/extras/src/main/java/com/google/gson/typeadapters/PostConstructAdapterFactory.java +++ b/extras/src/main/java/com/google/gson/typeadapters/PostConstructAdapterFactory.java @@ -38,7 +38,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { if (m.isAnnotationPresent(PostConstruct.class)) { m.setAccessible(true); TypeAdapter delegate = gson.getDelegateAdapter(this, type); - return new PostConstructAdapter(delegate, m); + return new PostConstructAdapter<>(delegate, m); } } } diff --git a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java index c496491ac6..502ad4ecd7 100644 --- a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java +++ b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -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; @@ -27,10 +23,12 @@ import com.google.gson.JsonPrimitive; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.Streams; 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 @@ -136,11 +134,13 @@ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { private final Class baseType; private final String typeFieldName; - private final Map> labelToSubtype = new LinkedHashMap>(); - private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + private final Map> labelToSubtype = new LinkedHashMap<>(); + private final Map, 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(); } @@ -152,10 +152,11 @@ private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boole /** * 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 RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { - return new RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType); + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); } /** @@ -163,7 +164,7 @@ public static RuntimeTypeAdapterFactory of(Class baseType, String type * typeFieldName} as the type field name. Type field names are case sensitive. */ public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { - return new RuntimeTypeAdapterFactory(baseType, typeFieldName, false); + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); } /** @@ -171,7 +172,16 @@ public static RuntimeTypeAdapterFactory of(Class baseType, String type * the type field name. */ public static RuntimeTypeAdapterFactory of(Class baseType) { - return new RuntimeTypeAdapterFactory(baseType, "type", false); + 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 recognizeSubtypes() { + this.recognizeSubtypes = true; + return this; } /** @@ -204,15 +214,21 @@ public RuntimeTypeAdapterFactory registerSubtype(Class type) { return registerSubtype(type, type.getSimpleName()); } + @Override public TypeAdapter create(Gson gson, TypeToken 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; } - final Map> labelToDelegate - = new LinkedHashMap>(); - final Map, TypeAdapter> subtypeToDelegate - = new LinkedHashMap, TypeAdapter>(); + final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); + final Map> labelToDelegate = new LinkedHashMap<>(); + final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); for (Map.Entry> entry : labelToSubtype.entrySet()) { TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); labelToDelegate.put(entry.getKey(), delegate); @@ -221,7 +237,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { return new TypeAdapter() { @Override public R read(JsonReader in) throws IOException { - JsonElement jsonElement = Streams.parse(in); + JsonElement jsonElement = jsonElementAdapter.read(in); JsonElement labelJsonElement; if (maintainType) { labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); @@ -255,7 +271,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); if (maintainType) { - Streams.write(jsonObject, out); + jsonElementAdapter.write(out, jsonObject); return; } @@ -270,7 +286,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { for (Map.Entry e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); } - Streams.write(clone, out); + jsonElementAdapter.write(out, clone); } }.nullSafe(); } diff --git a/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java b/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java index 8a1d7cdbfb..3b2425ec9e 100644 --- a/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java +++ b/extras/src/test/java/com/google/gson/graph/GraphAdapterBuilderTest.java @@ -16,16 +16,22 @@ package com.google.gson.graph; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import junit.framework.TestCase; -public final class GraphAdapterBuilderTest extends TestCase { +import org.junit.Test; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +public final class GraphAdapterBuilderTest { + @Test public void testSerialization() { Roshambo rock = new Roshambo("ROCK"); Roshambo scissors = new Roshambo("SCISSORS"); @@ -46,6 +52,7 @@ public void testSerialization() { gson.toJson(rock).replace('"', '\'')); } + @Test public void testDeserialization() { String json = "{'0x1':{'name':'ROCK','beats':'0x2'}," + "'0x2':{'name':'SCISSORS','beats':'0x3'}," + @@ -66,20 +73,7 @@ public void testDeserialization() { assertSame(rock, paper.beats); } - public void testSerializationDirectSelfReference() { - Roshambo suicide = new Roshambo("SUICIDE"); - suicide.beats = suicide; - - GsonBuilder gsonBuilder = new GsonBuilder(); - new GraphAdapterBuilder() - .addType(Roshambo.class) - .registerOn(gsonBuilder); - Gson gson = gsonBuilder.create(); - - assertEquals("{'0x1':{'name':'SUICIDE','beats':'0x1'}}", - gson.toJson(suicide).replace('"', '\'')); - } - + @Test public void testDeserializationDirectSelfReference() { String json = "{'0x1':{'name':'SUICIDE','beats':'0x1'}}"; @@ -94,13 +88,14 @@ public void testDeserializationDirectSelfReference() { assertSame(suicide, suicide.beats); } + @Test public void testSerializeListOfLists() { Type listOfListsType = new TypeToken>>() {}.getType(); Type listOfAnyType = new TypeToken>() {}.getType(); - List> listOfLists = new ArrayList>(); + List> listOfLists = new ArrayList<>(); listOfLists.add(listOfLists); - listOfLists.add(new ArrayList()); + listOfLists.add(new ArrayList<>()); GsonBuilder gsonBuilder = new GsonBuilder(); new GraphAdapterBuilder() @@ -113,6 +108,7 @@ public void testSerializeListOfLists() { assertEquals("{'0x1':['0x1','0x2'],'0x2':[]}", json.replace('"', '\'')); } + @Test public void testDeserializeListOfLists() { Type listOfAnyType = new TypeToken>() {}.getType(); Type listOfListsType = new TypeToken>>() {}.getType(); @@ -130,6 +126,7 @@ public void testDeserializeListOfLists() { assertEquals(Collections.emptyList(), listOfLists.get(1)); } + @Test public void testSerializationWithMultipleTypes() { Company google = new Company("Google"); new Employee("Jesse", google); @@ -148,6 +145,7 @@ public void testSerializationWithMultipleTypes() { gson.toJson(google).replace('"', '\'')); } + @Test public void testDeserializationWithMultipleTypes() { GsonBuilder gsonBuilder = new GsonBuilder(); new GraphAdapterBuilder() @@ -189,7 +187,7 @@ static class Employee { static class Company { final String name; - final List employees = new ArrayList(); + final List employees = new ArrayList<>(); Company(String name) { this.name = name; } diff --git a/extras/src/test/java/com/google/gson/interceptors/InterceptorTest.java b/extras/src/test/java/com/google/gson/interceptors/InterceptorTest.java index 0aab6598c3..39d618a527 100644 --- a/extras/src/test/java/com/google/gson/interceptors/InterceptorTest.java +++ b/extras/src/test/java/com/google/gson/interceptors/InterceptorTest.java @@ -141,7 +141,7 @@ public User(String name, String password) { } public static final class UserValidator implements JsonPostDeserializer { - public void postDeserialize(User user) { + @Override public void postDeserialize(User user) { if (user.name == null || user.password == null) { throw new JsonSyntaxException("name and password are required fields."); } @@ -161,7 +161,7 @@ private static final class Address { } public static final class AddressValidator implements JsonPostDeserializer
{ - public void postDeserialize(Address address) { + @Override public void postDeserialize(Address address) { if (address.city == null || address.state == null || address.zip == null) { throw new JsonSyntaxException("Address city, state and zip are required fields."); } diff --git a/extras/src/test/java/com/google/gson/typeadapters/PostConstructAdapterFactoryTest.java b/extras/src/test/java/com/google/gson/typeadapters/PostConstructAdapterFactoryTest.java index 7bd0a520d9..e3574bbc37 100644 --- a/extras/src/test/java/com/google/gson/typeadapters/PostConstructAdapterFactoryTest.java +++ b/extras/src/test/java/com/google/gson/typeadapters/PostConstructAdapterFactoryTest.java @@ -70,6 +70,7 @@ public Sandwich(String bread, String cheese) { } } + @Override public boolean equals(Object o) { if (o == this) { return true; @@ -95,6 +96,7 @@ public MultipleSandwiches(List sandwiches) { this.sandwiches = sandwiches; } + @Override public boolean equals(Object o) { if (o == this) { return true; diff --git a/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java b/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java index 8c62bef7f4..5159001c97 100644 --- a/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java +++ b/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java @@ -41,6 +41,27 @@ public void testRuntimeTypeAdapter() { 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 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) diff --git a/extras/src/test/java/com/google/gson/typeadapters/UtcDateTypeAdapterTest.java b/extras/src/test/java/com/google/gson/typeadapters/UtcDateTypeAdapterTest.java index 56e54290cf..fe4104fb1c 100644 --- a/extras/src/test/java/com/google/gson/typeadapters/UtcDateTypeAdapterTest.java +++ b/extras/src/test/java/com/google/gson/typeadapters/UtcDateTypeAdapterTest.java @@ -16,18 +16,16 @@ package com.google.gson.typeadapters; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; - -import com.google.gson.JsonParseException; import junit.framework.TestCase; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - public final class UtcDateTypeAdapterTest extends TestCase { private final Gson gson = new GsonBuilder() .registerTypeAdapter(Date.class, new UtcDateTypeAdapter()) @@ -83,7 +81,7 @@ public void testWellFormedParseException() { gson.fromJson("2017-06-20T14:32:30", Date.class); fail("No exception"); } catch (JsonParseException exe) { - assertEquals(exe.getMessage(), "java.text.ParseException: Failed to parse date ['2017-06-20T14']: 2017-06-20T14"); + assertEquals("java.text.ParseException: Failed to parse date ['2017-06-20T14']: 2017-06-20T14", exe.getMessage()); } } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7a3265ee94..0000000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 4c5f882001..0000000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Apr 27 17:41:01 PDT 2018 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/gradlew b/gradlew deleted file mode 100755 index cccdd3d517..0000000000 --- a/gradlew +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env sh - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100755 index f9553162f1..0000000000 --- a/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/gson/README b/gson/README deleted file mode 100644 index a925a5cd0c..0000000000 --- a/gson/README +++ /dev/null @@ -1,7 +0,0 @@ -Gson is a Java library that can be used to convert Java Objects into their -JSON representation. It can also be used to convert a JSON string to an -equivalent Java object. Gson can work with arbitrary Java objects including -pre-existing objects that you do not have source-code of. - -Complete Gson documentation is available at its project page -https://github.com/google/gson diff --git a/gson/README.md b/gson/README.md new file mode 100644 index 0000000000..75ec9fc92a --- /dev/null +++ b/gson/README.md @@ -0,0 +1,4 @@ +# gson + +This Maven module contains the Gson source code. The artifacts created by this module +are deployed to Maven Central under the coordinates `com.google.code.gson:gson`. diff --git a/gson/bnd.bnd b/gson/bnd.bnd index 57a8657fd8..626a0c5bec 100644 --- a/gson/bnd.bnd +++ b/gson/bnd.bnd @@ -3,8 +3,12 @@ Bundle-Name: ${project.name} Bundle-Description: ${project.description} Bundle-Vendor: Google Gson Project Bundle-ContactAddress: ${project.parent.url} -Bundle-RequiredExecutionEnvironment: JavaSE-1.6, JavaSE-1.7, JavaSE-1.8 -Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))" +Bundle-RequiredExecutionEnvironment: JavaSE-1.7, JavaSE-1.8 +Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.7))" + +# Optional dependency for JDK's sun.misc.Unsafe +# https://bnd.bndtools.org/chapters/920-faq.html#remove-unwanted-imports- +Import-Package: sun.misc;resolution:=optional, * -removeheaders: Private-Package diff --git a/gson/build.gradle b/gson/build.gradle deleted file mode 100644 index 4dd24c1d0f..0000000000 --- a/gson/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ -apply plugin: 'java' -apply plugin: 'maven' - -group = 'com.google.code.gson' -version = '2.8.6-SNAPSHOT' - -sourceCompatibility = 1.6 -targetCompatibility = 1.6 - -sourceSets.main.java.exclude("**/module-info.java") -dependencies { - testCompile "junit:junit:4.12" -} diff --git a/gson/pom.xml b/gson/pom.xml index cf32962ad0..20c23d62c2 100644 --- a/gson/pom.xml +++ b/gson/pom.xml @@ -4,12 +4,19 @@ com.google.code.gson gson-parent - 2.8.7-SNAPSHOT + 2.9.2-SNAPSHOT gson Gson + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + + + junit @@ -17,24 +24,78 @@ test - + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-compile + + + + module-info.java + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M7 + + + + --illegal-access=deny + + org.apache.maven.plugins maven-javadoc-plugin - com.google.gson com.google.gson.internal:com.google.gson.internal.bind - - https://docs.oracle.com/javase/6/docs/api/ - + + + + org.moditect + moditect-maven-plugin + 1.0.0.RC2 + + + add-module-info + package + + add-module-info + + + 9 + + ${project.build.sourceDirectory}/module-info.java + + + true + + + + biz.aQute.bnd bnd-maven-plugin - 4.0.0 + 6.3.1 @@ -48,6 +109,7 @@ maven-jar-plugin + ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -69,6 +131,81 @@ + + com.coderplus.maven.plugins + copy-rename-maven-plugin + 1.0.1 + + + pre-obfuscate-class + process-test-classes + + rename + + + + + ${project.build.directory}/test-classes/com/google/gson/functional/EnumWithObfuscatedTest.class + ${project.build.directory}/test-classes-obfuscated-injar/com/google/gson/functional/EnumWithObfuscatedTest.class + + + ${project.build.directory}/test-classes/com/google/gson/functional/EnumWithObfuscatedTest$Gender.class + ${project.build.directory}/test-classes-obfuscated-injar/com/google/gson/functional/EnumWithObfuscatedTest$Gender.class + + + + + + + + com.github.wvengen + proguard-maven-plugin + 2.6.0 + + + process-test-classes + + proguard + + + + + true + test-classes-obfuscated-injar + test-classes-obfuscated-outjar + **/*.class + ${basedir}/src/test/resources/testcases-proguard.conf + + ${project.build.directory}/classes + ${java.home}/jmods/java.base.jmod + + + + + maven-resources-plugin + 3.3.0 + + + post-obfuscate-class + process-test-classes + + copy-resources + + + ${project.build.directory}/test-classes/com/google/gson/functional + + + ${project.build.directory}/test-classes-obfuscated-outjar/com/google/gson/functional + + EnumWithObfuscatedTest.class + EnumWithObfuscatedTest$Gender.class + + + + + + + diff --git a/gson/src/main/java/com/google/gson/FieldAttributes.java b/gson/src/main/java/com/google/gson/FieldAttributes.java index 4ee906a60a..9fb93f7bec 100644 --- a/gson/src/main/java/com/google/gson/FieldAttributes.java +++ b/gson/src/main/java/com/google/gson/FieldAttributes.java @@ -135,27 +135,8 @@ public boolean hasModifier(int modifier) { 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(); } } diff --git a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java index ddb9a93d62..a4fa7c2715 100644 --- a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java +++ b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java @@ -44,7 +44,7 @@ public enum FieldNamingPolicy implements FieldNamingStrategy { * Using this naming policy with Gson will ensure that the first "letter" of the Java * field name is capitalized when serialized to its JSON form. * - *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *

Here are a few examples of the form "Java Field Name" ---> "JSON Field Name":

*