From 6b9db2e44948d410b2dbede6a4a8667782d6c04b Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 19 Sep 2022 15:47:11 +0200 Subject: [PATCH] Add Gson.fromJson(..., TypeToken) overloads (#1700) * Add Gson.fromJson(..., TypeToken) overloads Previously only Gson.fromJson(..., Type) existed which is however not type-safe since the generic type parameter T used for the return type is not bound. Since these methods are often used in the form gson.fromJson(..., new TypeToken<...>(){}.getType()) this commit now adds overloads which accept a TypeToken and are therefore more type-safe. Additional changes: - Fixed some grammar mistakes - Added javadoc @see tags - Consistently write "JSON" in uppercase - More precise placement of @SuppressWarnings("unchecked") * Add to Gson.fromJson javadoc that JSON is fully consumed The newly added documentation deliberately does not state which exception is thrown because Gson.assertFullConsumption could throw either a JsonIOException or a JsonSyntaxException. * Remove unnecessary wrapping and unwrapping as TypeToken in Gson.fromJson Since the actual implementation of Gson.fromJson is TypeToken based, the TypeToken variant overloads are now the "main" implementation and the other overloads delegate to them. Previously the Type variant overloads were the "main" implementation which caused `TypeToken.getType()` followed by `TypeToken.get(...)` when the TypeToken variant overloads were used. * Trim source code whitespaces * Fix Gson.fromJson(JsonReader, Class) not casting read Object To be consistent with the other Gson.fromJson(..., Class) overloads the method should cast the result. * Replace User Guide link in Gson documentation * Remove more references to fromJson(..., Type) * Extend documentation for fromJson(JsonReader, ...) * Replace some TypeToken.getType() usages * Address feedback; improve documentation * Remove fromJson(JsonReader, Class) again As noticed during review adding this method is source incompatible. --- UserGuide.md | 4 +- gson/src/main/java/com/google/gson/Gson.java | 360 +++++++++++++----- .../com/google/gson/reflect/TypeToken.java | 20 +- .../java/com/google/gson/MixedStreamTest.java | 2 +- .../functional/ParameterizedTypesTest.java | 76 +++- .../CollectionsDeserializationBenchmark.java | 9 +- .../google/gson/metrics/ParseBenchmark.java | 4 +- 7 files changed, 364 insertions(+), 111 deletions(-) diff --git a/UserGuide.md b/UserGuide.md index 12b53351bd..2aafb067e8 100644 --- a/UserGuide.md +++ b/UserGuide.md @@ -225,7 +225,7 @@ Collection ints = Arrays.asList(1,2,3,4,5); String json = gson.toJson(ints); // ==> json is [1,2,3,4,5] // Deserialization -Type collectionType = new TypeToken>(){}.getType(); +TypeToken> collectionType = new TypeToken>(){}; Collection ints2 = gson.fromJson(json, collectionType); // ==> ints2 is same as ints ``` @@ -263,7 +263,7 @@ For deserialization Gson uses the `read` method of the `TypeAdapter` registered ```java Gson gson = new Gson(); -Type mapType = new TypeToken>(){}.getType(); +TypeToken> mapType = new TypeToken>(){}; String json = "{\"key\": \"value\"}"; // Deserialization diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 168bc6f1c4..c3262a6fe0 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -76,26 +76,32 @@ *
  * Gson gson = new Gson(); // Or use new GsonBuilder().create();
  * MyType target = new MyType();
- * String json = gson.toJson(target); // serializes target to Json
+ * String json = gson.toJson(target); // serializes target to JSON
  * MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
  * 
* - *

If the object that your are serializing/deserializing is a {@code ParameterizedType} - * (i.e. contains at least one type parameter and may be an array) then you must use the - * {@link #toJson(Object, Type)} or {@link #fromJson(String, Type)} method. Here is an - * example for serializing and deserializing a {@code ParameterizedType}: - * + *

If the type of the object that you are converting is a {@code ParameterizedType} + * (i.e. has at least one type argument, for example {@code List}) then for + * deserialization you must use a {@code fromJson} method with {@link Type} or {@link TypeToken} + * parameter to specify the parameterized type. For serialization specifying a {@code Type} + * or {@code TypeToken} is optional, otherwise Gson will use the runtime type of the object. + * {@link TypeToken} is a class provided by Gson which helps creating parameterized types. + * Here is an example showing how this can be done: *

- * Type listType = new TypeToken<List<String>>() {}.getType();
- * List<String> target = new LinkedList<String>();
- * target.add("blah");
+ * TypeToken<List<MyType>> listType = new TypeToken<List<MyType>>() {};
+ * List<MyType> target = new LinkedList<MyType>();
+ * target.add(new MyType(1, "abc"));
  *
  * Gson gson = new Gson();
- * String json = gson.toJson(target, listType);
- * List<String> target2 = gson.fromJson(json, listType);
+ * // For serialization you normally do not have to specify the type, Gson will use
+ * // the runtime type of the objects, however you can also specify it explicitly
+ * String json = gson.toJson(target, listType.getType());
+ *
+ * // But for deserialization you have to specify the type
+ * List<MyType> target2 = gson.fromJson(json, listType);
  * 
* - *

See the Gson User Guide + *

See the Gson User Guide * for a more complete set of examples.

* *

Lenient JSON handling

@@ -125,7 +131,7 @@ * to make sure there is no trailing data * * - * @see com.google.gson.reflect.TypeToken + * @see TypeToken * * @author Inderjeet Singh * @author Joel Leitch @@ -210,9 +216,9 @@ public final class Gson { * through {@link GsonBuilder#excludeFieldsWithoutExposeAnnotation()}. *
  • By default, Gson ignores the {@link com.google.gson.annotations.Since} annotation. You * can enable Gson to use this annotation through {@link GsonBuilder#setVersion(double)}.
  • - *
  • The default field naming policy for the output Json is same as in Java. So, a Java class + *
  • The default field naming policy for the output JSON is same as in Java. So, a Java class * field versionNumber will be output as "versionNumber" in - * Json. The same rules are applied for mapping incoming Json to the Java classes. You can + * JSON. The same rules are applied for mapping incoming JSON to the Java classes. You can * change this policy through {@link GsonBuilder#setFieldNamingPolicy(FieldNamingPolicy)}.
  • *
  • By default, Gson excludes transient or static fields from * consideration for serialization and deserialization. You can change this behavior through @@ -644,13 +650,15 @@ public TypeAdapter getAdapter(Class type) { * {@link JsonElement}s. This method should be used when the specified object is not a generic * type. This method uses {@link Class#getClass()} to get the type for the specified object, but * the {@code getClass()} loses the generic type information because of the Type Erasure feature - * of Java. Note that this method works fine if the any of the object fields are of generic type, + * of Java. Note that this method works fine if any of the object fields are of generic type, * just the object itself should not be of a generic type. If the object is of generic type, use * {@link #toJsonTree(Object, Type)} instead. * - * @param src the object for which Json representation is to be created setting for Gson - * @return Json representation of {@code src}. + * @param src the object for which JSON representation is to be created + * @return JSON representation of {@code src}. * @since 1.4 + * + * @see #toJsonTree(Object, Type) */ public JsonElement toJsonTree(Object src) { if (src == null) { @@ -674,6 +682,8 @@ public JsonElement toJsonTree(Object src) { * * @return Json representation of {@code src} * @since 1.4 + * + * @see #toJsonTree(Object) */ public JsonElement toJsonTree(Object src, Type typeOfSrc) { JsonTreeWriter writer = new JsonTreeWriter(); @@ -682,17 +692,20 @@ public JsonElement toJsonTree(Object src, Type typeOfSrc) { } /** - * This method serializes the specified object into its equivalent Json representation. + * This method serializes the specified object into its equivalent JSON representation. * This method should be used when the specified object is not a generic type. This method uses * {@link Class#getClass()} to get the type for the specified object, but the * {@code getClass()} loses the generic type information because of the Type Erasure feature - * of Java. Note that this method works fine if the any of the object fields are of generic type, + * of Java. Note that this method works fine if any of the object fields are of generic type, * just the object itself should not be of a generic type. If the object is of generic type, use * {@link #toJson(Object, Type)} instead. If you want to write out the object to a * {@link Writer}, use {@link #toJson(Object, Appendable)} instead. * - * @param src the object for which Json representation is to be created setting for Gson + * @param src the object for which JSON representation is to be created * @return Json representation of {@code src}. + * + * @see #toJson(Object, Appendable) + * @see #toJson(Object, Type) */ public String toJson(Object src) { if (src == null) { @@ -703,7 +716,7 @@ public String toJson(Object src) { /** * This method serializes the specified object, including those of generic types, into its - * equivalent Json representation. This method must be used if the specified object is a generic + * equivalent JSON representation. This method must be used if the specified object is a generic * type. For non-generic objects, use {@link #toJson(Object)} instead. If you want to write out * the object to a {@link Appendable}, use {@link #toJson(Object, Type, Appendable)} instead. * @@ -714,7 +727,10 @@ public String toJson(Object src) { *
        * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
        * 
    - * @return Json representation of {@code src} + * @return JSON representation of {@code src} + * + * @see #toJson(Object, Type, Appendable) + * @see #toJson(Object) */ public String toJson(Object src, Type typeOfSrc) { StringWriter writer = new StringWriter(); @@ -723,18 +739,22 @@ public String toJson(Object src, Type typeOfSrc) { } /** - * This method serializes the specified object into its equivalent Json representation. + * This method serializes the specified object into its equivalent JSON representation and + * writes it to the writer. * This method should be used when the specified object is not a generic type. This method uses * {@link Class#getClass()} to get the type for the specified object, but the * {@code getClass()} loses the generic type information because of the Type Erasure feature - * of Java. Note that this method works fine if the any of the object fields are of generic type, + * of Java. Note that this method works fine if any of the object fields are of generic type, * just the object itself should not be of a generic type. If the object is of generic type, use * {@link #toJson(Object, Type, Appendable)} instead. * - * @param src the object for which Json representation is to be created setting for Gson - * @param writer Writer to which the Json representation needs to be written + * @param src the object for which JSON representation is to be created + * @param writer Writer to which the JSON representation needs to be written * @throws JsonIOException if there was a problem writing to the writer * @since 1.2 + * + * @see #toJson(Object) + * @see #toJson(Object, Type, Appendable) */ public void toJson(Object src, Appendable writer) throws JsonIOException { if (src != null) { @@ -746,8 +766,9 @@ public void toJson(Object src, Appendable writer) throws JsonIOException { /** * This method serializes the specified object, including those of generic types, into its - * equivalent Json representation. This method must be used if the specified object is a generic - * type. For non-generic objects, use {@link #toJson(Object, Appendable)} instead. + * equivalent JSON representation and writes it to the writer. + * This method must be used if the specified object is a generic type. For non-generic objects, + * use {@link #toJson(Object, Appendable)} instead. * * @param src the object for which JSON representation is to be created * @param typeOfSrc The specific genericized type of src. You can obtain @@ -756,9 +777,12 @@ public void toJson(Object src, Appendable writer) throws JsonIOException { *
        * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
        * 
    - * @param writer Writer to which the Json representation of src needs to be written. + * @param writer Writer to which the JSON representation of src needs to be written. * @throws JsonIOException if there was a problem writing to the writer * @since 1.2 + * + * @see #toJson(Object, Type) + * @see #toJson(Object, Appendable) */ public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException { try { @@ -824,7 +848,7 @@ public String toJson(JsonElement jsonElement) { * Writes out the equivalent JSON for a tree of {@link JsonElement}s. * * @param jsonElement root of a tree of {@link JsonElement}s - * @param writer Writer to which the Json representation needs to be written + * @param writer Writer to which the JSON representation needs to be written * @throws JsonIOException if there was a problem writing to the writer * @since 1.4 */ @@ -913,17 +937,17 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce } /** - * This method deserializes the specified Json into an object of the specified class. It is not + * This method deserializes the specified JSON into an object of the specified class. It is not * suitable to use if the specified class is a generic type since it will not have the generic * type information because of the Type Erasure feature of Java. Therefore, this method should not * be used if the desired type is a generic type. Note that this method works fine if the any of * the fields of the specified object are generics, just the object itself should not be a * generic type. For the cases when the object is of generic type, invoke - * {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of + * {@link #fromJson(String, TypeToken)}. If you have the JSON in a {@link Reader} instead of * a String, use {@link #fromJson(Reader, Class)} instead. * - *

    An exception is thrown if the JSON string has multiple top-level JSON elements, - * or if there is trailing data. + *

    An exception is thrown if the JSON string has multiple top-level JSON elements, or if there + * is trailing data. Use {@link #fromJson(JsonReader, Type)} if this behavior is not desired. * * @param the type of the desired object * @param json the string from which the object is to be deserialized @@ -932,98 +956,165 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce * or if {@code json} is empty. * @throws JsonSyntaxException if json is not a valid representation for an object of type * classOfT + * + * @see #fromJson(Reader, Class) + * @see #fromJson(String, TypeToken) */ public T fromJson(String json, Class classOfT) throws JsonSyntaxException { - Object object = fromJson(json, (Type) classOfT); + T object = fromJson(json, TypeToken.get(classOfT)); return Primitives.wrap(classOfT).cast(object); } /** - * This method deserializes the specified Json into an object of the specified type. This method + * This method deserializes the specified JSON into an object of the specified type. This method * is useful if the specified object is a generic type. For non-generic objects, use - * {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of + * {@link #fromJson(String, Class)} instead. If you have the JSON in a {@link Reader} instead of * a String, use {@link #fromJson(Reader, Type)} instead. * + *

    Since {@code Type} is not parameterized by T, this method is not type-safe and + * should be used carefully. If you are creating the {@code Type} from a {@link TypeToken}, + * prefer using {@link #fromJson(String, TypeToken)} instead since its return type is based + * on the {@code TypeToken} and is therefore more type-safe. + * *

    An exception is thrown if the JSON string has multiple top-level JSON elements, - * or if there is trailing data. + * or if there is trailing data. Use {@link #fromJson(JsonReader, Type)} if this behavior is + * not desired. * * @param 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 - * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for + * @param typeOfT The specific genericized type of src + * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null} + * or if {@code json} is empty. + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * + * @see #fromJson(Reader, Type) + * @see #fromJson(String, Class) + * @see #fromJson(String, TypeToken) + */ + @SuppressWarnings("unchecked") + public T fromJson(String json, Type typeOfT) throws JsonSyntaxException { + return (T) fromJson(json, TypeToken.get(typeOfT)); + } + + /** + * This method deserializes the specified JSON into an object of the specified type. This method + * is useful if the specified object is a generic type. For non-generic objects, use + * {@link #fromJson(String, Class)} instead. If you have the JSON in a {@link Reader} instead of + * a String, use {@link #fromJson(Reader, TypeToken)} instead. + * + *

    An exception is thrown if the JSON string has multiple top-level JSON elements, or if there + * is trailing data. Use {@link #fromJson(JsonReader, TypeToken)} if this behavior is not desired. + * + * @param 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 should create an anonymous subclass of + * {@code TypeToken} with the specific generic type arguments. For example, to get the type for * {@code Collection}, you should use: *

    -   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
    +   * new TypeToken<Collection<Foo>>(){}
        * 
    * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. - * @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 + * @throws JsonSyntaxException if json is not a valid representation for an object of the type typeOfT + * + * @see #fromJson(Reader, TypeToken) + * @see #fromJson(String, Class) */ - public T fromJson(String json, Type typeOfT) throws JsonSyntaxException { + public T fromJson(String json, TypeToken typeOfT) throws JsonSyntaxException { if (json == null) { return null; } StringReader reader = new StringReader(json); - @SuppressWarnings("unchecked") - T target = (T) fromJson(reader, typeOfT); - return target; + return fromJson(reader, typeOfT); } /** - * This method deserializes the Json read from the specified reader into an object of the + * This method deserializes the JSON read from the specified reader into an object of the * specified class. It is not suitable to use if the specified class is a generic type since it * will not have the generic type information because of the Type Erasure feature of Java. * Therefore, this method should not be used if the desired type is a generic type. Note that - * this method works fine if the any of the fields of the specified object are generics, just the + * this method works fine if any of the fields of the specified object are generics, just the * object itself should not be a generic type. For the cases when the object is of generic type, - * invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a + * invoke {@link #fromJson(Reader, TypeToken)}. If you have the JSON in a String form instead of a * {@link Reader}, use {@link #fromJson(String, Class)} instead. * - *

    An exception is thrown if the JSON data has multiple top-level JSON elements, - * or if there is trailing data. + *

    An exception is thrown if the JSON data has multiple top-level JSON elements, or if there + * is trailing data. Use {@link #fromJson(JsonReader, Type)} if this behavior is not desired. * * @param the type of the desired object - * @param json the reader producing the Json from which the object is to be deserialized. + * @param json the reader producing the JSON from which the object is to be deserialized. * @param classOfT the class of T - * @return an object of type T from the string. Returns {@code null} if {@code json} is at EOF. + * @return an object of type T from the Reader. Returns {@code null} if {@code json} is at EOF. * @throws JsonIOException if there was a problem reading from the Reader - * @throws JsonSyntaxException if json is not a valid representation for an object of type + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT * @since 1.2 + * + * @see #fromJson(String, Class) + * @see #fromJson(Reader, TypeToken) */ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { - JsonReader jsonReader = newJsonReader(json); - Object object = fromJson(jsonReader, classOfT); - assertFullConsumption(object, jsonReader); + T object = fromJson(json, TypeToken.get(classOfT)); return Primitives.wrap(classOfT).cast(object); } /** - * This method deserializes the Json read from the specified reader into an object of the + * This method deserializes the JSON read from the specified reader into an object of the * specified type. This method is useful if the specified object is a generic type. For - * non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a + * 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. * - *

    An exception is thrown if the JSON data has multiple top-level JSON elements, - * or if there is trailing data. + *

    Since {@code Type} is not parameterized by T, this method is not type-safe and + * should be used carefully. If you are creating the {@code Type} from a {@link TypeToken}, + * prefer using {@link #fromJson(Reader, TypeToken)} instead since its return type is based + * on the {@code TypeToken} and is therefore more type-safe. + * + *

    An exception is thrown if the JSON data has multiple top-level JSON elements, or if there + * is trailing data. Use {@link #fromJson(JsonReader, Type)} if this behavior is not desired. + * + * @param 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 + * @return an object of type T from the Reader. Returns {@code null} if {@code json} is at EOF. + * @throws JsonIOException if there was a problem reading from the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * @since 1.2 + * + * @see #fromJson(String, Type) + * @see #fromJson(Reader, Class) + * @see #fromJson(Reader, TypeToken) + */ + @SuppressWarnings("unchecked") + public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { + return (T) fromJson(json, TypeToken.get(typeOfT)); + } + + /** + * This method deserializes the JSON read from the specified reader into an object of the + * specified type. This method is useful if the specified object is a generic type. For + * 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, TypeToken)} instead. + * + *

    An exception is thrown if the JSON data has multiple top-level JSON elements, or if there + * is trailing data. Use {@link #fromJson(JsonReader, TypeToken)} if this behavior is not desired. * * @param 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 - * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for + * @param json the reader producing JSON from which the object is to be deserialized + * @param typeOfT The specific genericized type of src. You should create an anonymous subclass of + * {@code TypeToken} with the specific generic type arguments. For example, to get the type for * {@code Collection}, you should use: *

    -   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
    +   * new TypeToken<Collection<Foo>>(){}
        * 
    - * @return an object of type T from the json. Returns {@code null} if {@code json} is at EOF. + * @return an object of type T from the Reader. Returns {@code null} if {@code json} is at EOF. * @throws JsonIOException if there was a problem reading from the Reader - * @throws JsonSyntaxException if json is not a valid representation for an object of type - * @since 1.2 + * @throws JsonSyntaxException if json is not a valid representation for an object of type of typeOfT + * + * @see #fromJson(String, TypeToken) + * @see #fromJson(Reader, Class) */ - public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { + public T fromJson(Reader json, TypeToken typeOfT) throws JsonIOException, JsonSyntaxException { JsonReader jsonReader = newJsonReader(json); - @SuppressWarnings("unchecked") - T object = (T) fromJson(jsonReader, typeOfT); + T object = fromJson(jsonReader, typeOfT); assertFullConsumption(object, jsonReader); return object; } @@ -1040,10 +1131,18 @@ private static void assertFullConsumption(Object obj, JsonReader reader) { } } + // fromJson(JsonReader, Class) is unfortunately missing and cannot be added now without breaking + // source compatibility in certain cases, see https://github.com/google/gson/pull/1700#discussion_r973764414 + /** - * Reads the next JSON value from {@code reader} and convert it to an object + * Reads the next JSON value from {@code reader} and converts 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 {@code Type} is not parameterized by T, this method is not type-safe and + * should be used carefully. If you are creating the {@code Type} from a {@link TypeToken}, + * prefer using {@link #fromJson(JsonReader, TypeToken)} instead since its return type is based + * on the {@code TypeToken} and is therefore more type-safe. If the provided type is a + * {@code Class} the {@code TypeToken} can be created with {@link TypeToken#get(Class)}. * *

    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. @@ -1052,19 +1151,58 @@ private static void assertFullConsumption(Object obj, JsonReader reader) { * 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 + * @param the type of the desired object + * @param reader the reader whose next JSON value should be deserialized + * @param typeOfT The specific genericized type of src + * @return an object of type T from the JsonReader. Returns {@code null} if {@code reader} is at EOF. + * @throws JsonIOException if there was a problem reading from the JsonReader + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * + * @see #fromJson(Reader, Type) + * @see #fromJson(JsonReader, TypeToken) */ + @SuppressWarnings("unchecked") public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException { + return (T) fromJson(reader, TypeToken.get(typeOfT)); + } + + /** + * Reads the next JSON value from {@code reader} and converts it to an object + * of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF. + * This method is useful if the specified object is a generic type. For non-generic objects, + * {@link #fromJson(JsonReader, Type)} can be called, or {@link TypeToken#get(Class)} can + * be used to create the type token. + * + *

    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. + * + *

    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. + * + * @param the type of the desired object + * @param reader the reader whose next JSON value should be deserialized + * @param typeOfT The specific genericized type of src. You should create an anonymous subclass of + * {@code TypeToken} with the specific generic type arguments. For example, to get the type for + * {@code Collection}, you should use: + *

    +   * new TypeToken<Collection<Foo>>(){}
    +   * 
    + * @return an object of type T from the JsonReader. Returns {@code null} if {@code reader} is at EOF. + * @throws JsonIOException if there was a problem reading from the JsonReader + * @throws JsonSyntaxException if json is not a valid representation for an object of the type typeOfT + * + * @see #fromJson(Reader, TypeToken) + * @see #fromJson(JsonReader, Type) + */ + public T fromJson(JsonReader reader, TypeToken typeOfT) throws JsonIOException, JsonSyntaxException { boolean isEmpty = true; boolean oldLenient = reader.isLenient(); reader.setLenient(true); try { reader.peek(); isEmpty = false; - @SuppressWarnings("unchecked") - TypeToken typeToken = (TypeToken) TypeToken.get(typeOfT); - TypeAdapter typeAdapter = getAdapter(typeToken); + TypeAdapter typeAdapter = getAdapter(typeOfT); T object = typeAdapter.read(reader); return object; } catch (EOFException e) { @@ -1091,52 +1229,86 @@ public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J } /** - * This method deserializes the Json read from the specified parse tree into an object of the + * This method deserializes the JSON read from the specified parse tree into an object of the * specified type. It is not suitable to use if the specified class is a generic type since it * will not have the generic type information because of the Type Erasure feature of Java. * Therefore, this method should not be used if the desired type is a generic type. Note that - * this method works fine if the any of the fields of the specified object are generics, just the + * this method works fine if any of the fields of the specified object are generics, just the * object itself should not be a generic type. For the cases when the object is of generic type, - * invoke {@link #fromJson(JsonElement, Type)}. + * invoke {@link #fromJson(JsonElement, TypeToken)}. + * * @param the type of the desired object * @param json the root of the parse tree of {@link JsonElement}s from which the object is to * be deserialized * @param classOfT The class of T - * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null} + * @return an object of type T from the JSON. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. - * @throws JsonSyntaxException 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 classOfT * @since 1.3 + * + * @see #fromJson(Reader, Class) + * @see #fromJson(JsonElement, TypeToken) */ public T fromJson(JsonElement json, Class classOfT) throws JsonSyntaxException { - Object object = fromJson(json, (Type) classOfT); + T object = fromJson(json, TypeToken.get(classOfT)); return Primitives.wrap(classOfT).cast(object); } /** - * This method deserializes the Json read from the specified parse tree into an object of the + * This method deserializes the JSON read from the specified parse tree into an object of the * specified type. This method is useful if the specified object is a generic type. For * non-generic objects, use {@link #fromJson(JsonElement, Class)} instead. * + *

    Since {@code Type} is not parameterized by T, this method is not type-safe and + * should be used carefully. If you are creating the {@code Type} from a {@link TypeToken}, + * prefer using {@link #fromJson(JsonElement, TypeToken)} instead since its return type is based + * on the {@code TypeToken} and is therefore more type-safe. + * * @param the type of the desired object * @param json the root of the parse tree of {@link JsonElement}s from which the object is to * be deserialized - * @param typeOfT The specific genericized type of src. You can obtain this type by using the - * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for + * @param typeOfT The specific genericized type of src + * @return an object of type T from the JSON. Returns {@code null} if {@code json} is {@code null} + * or if {@code json} is empty. + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * @since 1.3 + * + * @see #fromJson(Reader, Type) + * @see #fromJson(JsonElement, Class) + * @see #fromJson(JsonElement, TypeToken) + */ + @SuppressWarnings("unchecked") + public T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException { + return (T) fromJson(json, TypeToken.get(typeOfT)); + } + + /** + * This method deserializes the JSON read from the specified parse tree into an object of the + * specified type. This method is useful if the specified object is a generic type. For + * non-generic objects, use {@link #fromJson(JsonElement, Class)} instead. + * + * @param the type of the desired object + * @param json the root of the parse tree of {@link JsonElement}s from which the object is to + * be deserialized + * @param typeOfT The specific genericized type of src. You should create an anonymous subclass of + * {@code TypeToken} with the specific generic type arguments. For example, to get the type for * {@code Collection}, you should use: *

    -   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
    +   * new TypeToken<Collection<Foo>>(){}
        * 
    - * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null} + * @return an object of type T from the JSON. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT * @since 1.3 + * + * @see #fromJson(Reader, TypeToken) + * @see #fromJson(JsonElement, Class) */ - @SuppressWarnings("unchecked") - public T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException { + public T fromJson(JsonElement json, TypeToken typeOfT) throws JsonSyntaxException { if (json == null) { return null; } - return (T) fromJson(new JsonTreeReader(json), typeOfT); + return fromJson(new JsonTreeReader(json), typeOfT); } static class FutureTypeAdapter extends TypeAdapter { diff --git a/gson/src/main/java/com/google/gson/reflect/TypeToken.java b/gson/src/main/java/com/google/gson/reflect/TypeToken.java index 547e5f5b5f..39e81f33f9 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -32,7 +32,7 @@ * runtime. * *

    For example, to create a type literal for {@code List}, you can - * create an empty anonymous inner class: + * create an empty anonymous class: * *

    * {@code TypeToken> list = new TypeToken>() {};} @@ -43,6 +43,11 @@ * might expect, which gives a false sense of type-safety at compilation time * and can lead to an unexpected {@code ClassCastException} at runtime. * + *

    If the type arguments of the parameterized type are only available at + * runtime, for example when you want to create a {@code List} based on + * a {@code Class} representing the element type, the method + * {@link #getParameterized(Type, Type...)} can be used. + * * @author Bob Lee * @author Sven Mawson * @author Jesse Wilson @@ -317,8 +322,17 @@ public static TypeToken get(Class type) { } /** - * Gets type literal for the parameterized type represented by applying {@code typeArguments} to - * {@code rawType}. + * Gets a type literal for the parameterized type represented by applying {@code typeArguments} to + * {@code rawType}. This is mainly intended for situations where the type arguments are not + * available at compile time. The following example shows how a type token for {@code Map} + * can be created: + *

    {@code
    +   * Class keyClass = ...;
    +   * Class valueClass = ...;
    +   * TypeToken mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
    +   * }
    + * As seen here the result is a {@code TypeToken}; this method cannot provide any type safety, + * and care must be taken to pass in the correct number of type arguments. * * @throws IllegalArgumentException * If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for diff --git a/gson/src/test/java/com/google/gson/MixedStreamTest.java b/gson/src/test/java/com/google/gson/MixedStreamTest.java index 00eb4bc8a0..fa16659f0d 100644 --- a/gson/src/test/java/com/google/gson/MixedStreamTest.java +++ b/gson/src/test/java/com/google/gson/MixedStreamTest.java @@ -174,7 +174,7 @@ public void testReadNulls() { } catch (NullPointerException expected) { } try { - gson.fromJson(new JsonReader(new StringReader("true")), null); + gson.fromJson(new JsonReader(new StringReader("true")), (Type) null); fail(); } catch (NullPointerException expected) { } diff --git a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java index 49cb0db9bc..e616874620 100644 --- a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java +++ b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java @@ -16,13 +16,19 @@ package com.google.gson.functional; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import com.google.gson.ParameterizedTypeFixtures.MyParameterizedType; 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 com.google.gson.stream.JsonReader; import java.io.Reader; import java.io.Serializable; import java.io.StringReader; @@ -32,7 +38,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; /** * Functional tests for the serialization and deserialization of parameterized types in Gson. @@ -40,15 +47,15 @@ * @author Inderjeet Singh * @author Joel Leitch */ -public class ParameterizedTypesTest extends TestCase { +public class ParameterizedTypesTest { private Gson gson; - @Override - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() { gson = new Gson(); } + @Test public void testParameterizedTypesSerialization() throws Exception { MyParameterizedType src = new MyParameterizedType<>(10); Type typeOfSrc = new TypeToken>() {}.getType(); @@ -56,6 +63,7 @@ public void testParameterizedTypesSerialization() throws Exception { assertEquals(src.getExpectedJson(), json); } + @Test public void testParameterizedTypeDeserialization() throws Exception { BagOfPrimitives bag = new BagOfPrimitives(); MyParameterizedType expected = new MyParameterizedType<>(bag); @@ -70,6 +78,7 @@ public void testParameterizedTypeDeserialization() throws Exception { assertEquals(expected, actual); } + @Test public void testTypesWithMultipleParametersSerialization() throws Exception { MultiParameters src = new MultiParameters<>(10, 1.0F, 2.1D, "abc", new BagOfPrimitives()); @@ -81,6 +90,7 @@ public void testTypesWithMultipleParametersSerialization() throws Exception { assertEquals(expected, json); } + @Test public void testTypesWithMultipleParametersDeserialization() throws Exception { Type typeOfTarget = new TypeToken>() {}.getType(); @@ -93,6 +103,7 @@ public void testTypesWithMultipleParametersDeserialization() throws Exception { assertEquals(expected, target); } + @Test public void testParameterizedTypeWithCustomSerializer() { Type ptIntegerType = new TypeToken>() {}.getType(); Type ptStringType = new TypeToken>() {}.getType(); @@ -109,6 +120,7 @@ public void testParameterizedTypeWithCustomSerializer() { assertEquals(MyParameterizedTypeAdapter.getExpectedJson(stringTarget), json); } + @Test public void testParameterizedTypesWithCustomDeserializer() { Type ptIntegerType = new TypeToken>() {}.getType(); Type ptStringType = new TypeToken>() {}.getType(); @@ -130,6 +142,7 @@ public void testParameterizedTypesWithCustomDeserializer() { assertEquals("abc", stringTarget.value); } + @Test public void testParameterizedTypesWithWriterSerialization() throws Exception { Writer writer = new StringWriter(); MyParameterizedType src = new MyParameterizedType<>(10); @@ -138,6 +151,7 @@ public void testParameterizedTypesWithWriterSerialization() throws Exception { assertEquals(src.getExpectedJson(), writer.toString()); } + @Test public void testParameterizedTypeWithReaderDeserialization() throws Exception { BagOfPrimitives bag = new BagOfPrimitives(); MyParameterizedType expected = new MyParameterizedType<>(bag); @@ -158,6 +172,7 @@ private static T[] arrayOf(T... args) { return args; } + @Test public void testVariableTypeFieldsAndGenericArraysSerialization() throws Exception { Integer obj = 0; Integer[] array = { 1, 2, 3 }; @@ -174,6 +189,7 @@ public void testVariableTypeFieldsAndGenericArraysSerialization() throws Excepti assertEquals(objToSerialize.getExpectedJson(), json); } + @Test public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Exception { Integer obj = 0; Integer[] array = { 1, 2, 3 }; @@ -191,6 +207,7 @@ public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Excep assertEquals(objAfterDeserialization.getExpectedJson(), json); } + @Test public void testVariableTypeDeserialization() throws Exception { Type typeOfSrc = new TypeToken>() {}.getType(); ObjectWithTypeVariables objToSerialize = @@ -201,6 +218,7 @@ public void testVariableTypeDeserialization() throws Exception { assertEquals(objAfterDeserialization.getExpectedJson(), json); } + @Test public void testVariableTypeArrayDeserialization() throws Exception { Integer[] array = { 1, 2, 3 }; @@ -213,6 +231,7 @@ public void testVariableTypeArrayDeserialization() throws Exception { assertEquals(objAfterDeserialization.getExpectedJson(), json); } + @Test public void testParameterizedTypeWithVariableTypeDeserialization() throws Exception { List list = new ArrayList<>(); list.add(4); @@ -227,6 +246,7 @@ public void testParameterizedTypeWithVariableTypeDeserialization() throws Except assertEquals(objAfterDeserialization.getExpectedJson(), json); } + @Test public void testParameterizedTypeGenericArraysSerialization() throws Exception { List list = new ArrayList<>(); list.add(1); @@ -240,6 +260,7 @@ public void testParameterizedTypeGenericArraysSerialization() throws Exception { assertEquals("{\"arrayOfListOfTypeParameters\":[[1,2],[1,2]]}", json); } + @Test public void testParameterizedTypeGenericArraysDeserialization() throws Exception { List list = new ArrayList<>(); list.add(1); @@ -483,6 +504,7 @@ public static final class Amount int value = 30; } + @Test public void testDeepParameterizedTypeSerialization() { Amount amount = new Amount<>(); String json = gson.toJson(amount); @@ -490,6 +512,7 @@ public void testDeepParameterizedTypeSerialization() { assertTrue(json.contains("30")); } + @Test public void testDeepParameterizedTypeDeserialization() { String json = "{value:30}"; Type type = new TypeToken>() {}.getType(); @@ -497,4 +520,47 @@ public void testDeepParameterizedTypeDeserialization() { assertEquals(30, amount.value); } // End: tests to reproduce issue 103 + + private static void assertCorrectlyDeserialized(Object object) { + @SuppressWarnings("unchecked") + List list = (List) object; + assertEquals(1, list.size()); + assertEquals(4, list.get(0).q); + } + + @Test + public void testGsonFromJsonTypeToken() { + TypeToken> typeToken = new TypeToken>() {}; + Type type = typeToken.getType(); + + { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("q", 4); + JsonArray jsonArray = new JsonArray(); + jsonArray.add(jsonObject); + + assertCorrectlyDeserialized(gson.fromJson(jsonArray, typeToken)); + assertCorrectlyDeserialized(gson.fromJson(jsonArray, type)); + } + + String json = "[{\"q\":4}]"; + + { + assertCorrectlyDeserialized(gson.fromJson(json, typeToken)); + assertCorrectlyDeserialized(gson.fromJson(json, type)); + } + + { + assertCorrectlyDeserialized(gson.fromJson(new StringReader(json), typeToken)); + assertCorrectlyDeserialized(gson.fromJson(new StringReader(json), type)); + } + + { + JsonReader reader = new JsonReader(new StringReader(json)); + assertCorrectlyDeserialized(gson.fromJson(reader, typeToken)); + + reader = new JsonReader(new StringReader(json)); + assertCorrectlyDeserialized(gson.fromJson(reader, type)); + } + } } diff --git a/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java index dad0d99ae7..738b5ae4bf 100644 --- a/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java +++ b/metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java @@ -33,14 +33,15 @@ */ public class CollectionsDeserializationBenchmark { - private static final Type LIST_TYPE = new TypeToken>(){}.getType(); + private static final TypeToken> LIST_TYPE_TOKEN = new TypeToken>(){}; + private static final Type LIST_TYPE = LIST_TYPE_TOKEN.getType(); private Gson gson; private String json; public static void main(String[] args) { NonUploadingCaliperRunner.run(CollectionsDeserializationBenchmark.class, args); } - + @BeforeExperiment void setUp() throws Exception { this.gson = new Gson(); @@ -51,12 +52,12 @@ void setUp() throws Exception { this.json = gson.toJson(bags, LIST_TYPE); } - /** + /** * Benchmark to measure Gson performance for deserializing an object */ public void timeCollectionsDefault(int reps) { for (int i=0; i() {}, new TypeReference() {}), READER_LONG(new TypeToken() {}, new TypeReference() {}); - private final Type gsonType; + private final TypeToken gsonType; private final TypeReference jacksonType; private Document(TypeToken typeToken, TypeReference typeReference) { - this.gsonType = typeToken.getType(); + this.gsonType = typeToken; this.jacksonType = typeReference; } }