Skip to content

Commit

Permalink
Add Gson.fromJson(..., TypeToken) overloads (#1700)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Marcono1234 committed Sep 19, 2022
1 parent a733150 commit 6b9db2e
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 111 deletions.
4 changes: 2 additions & 2 deletions UserGuide.md
Expand Up @@ -225,7 +225,7 @@ Collection<Integer> 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<Collection<Integer>>(){}.getType();
TypeToken<Collection<Integer>> collectionType = new TypeToken<Collection<Integer>>(){};
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
// ==> ints2 is same as ints
```
Expand Down Expand Up @@ -263,7 +263,7 @@ For deserialization Gson uses the `read` method of the `TypeAdapter` registered

```java
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>(){};
String json = "{\"key\": \"value\"}";

// Deserialization
Expand Down
360 changes: 266 additions & 94 deletions gson/src/main/java/com/google/gson/Gson.java

Large diffs are not rendered by default.

20 changes: 17 additions & 3 deletions gson/src/main/java/com/google/gson/reflect/TypeToken.java
Expand Up @@ -32,7 +32,7 @@
* runtime.
*
* <p>For example, to create a type literal for {@code List<String>}, you can
* create an empty anonymous inner class:
* create an empty anonymous class:
*
* <p>
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
Expand All @@ -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.
*
* <p>If the type arguments of the parameterized type are only available at
* runtime, for example when you want to create a {@code List<E>} based on
* a {@code Class<E>} representing the element type, the method
* {@link #getParameterized(Type, Type...)} can be used.
*
* @author Bob Lee
* @author Sven Mawson
* @author Jesse Wilson
Expand Down Expand Up @@ -317,8 +322,17 @@ public static <T> TypeToken<T> get(Class<T> 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<K, V>}
* can be created:
* <pre>{@code
* Class<K> keyClass = ...;
* Class<V> valueClass = ...;
* TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
* }</pre>
* As seen here the result is a {@code TypeToken<?>}; this method cannot provide any type safety,
* 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
Expand Down
2 changes: 1 addition & 1 deletion gson/src/test/java/com/google/gson/MixedStreamTest.java
Expand Up @@ -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) {
}
Expand Down
Expand Up @@ -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;
Expand All @@ -32,30 +38,32 @@
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.
*
* @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<Integer> src = new MyParameterizedType<>(10);
Type typeOfSrc = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
String json = gson.toJson(src, typeOfSrc);
assertEquals(src.getExpectedJson(), json);
}

@Test
public void testParameterizedTypeDeserialization() throws Exception {
BagOfPrimitives bag = new BagOfPrimitives();
MyParameterizedType<BagOfPrimitives> expected = new MyParameterizedType<>(bag);
Expand All @@ -70,6 +78,7 @@ public void testParameterizedTypeDeserialization() throws Exception {
assertEquals(expected, actual);
}

@Test
public void testTypesWithMultipleParametersSerialization() throws Exception {
MultiParameters<Integer, Float, Double, String, BagOfPrimitives> src =
new MultiParameters<>(10, 1.0F, 2.1D, "abc", new BagOfPrimitives());
Expand All @@ -81,6 +90,7 @@ public void testTypesWithMultipleParametersSerialization() throws Exception {
assertEquals(expected, json);
}

@Test
public void testTypesWithMultipleParametersDeserialization() throws Exception {
Type typeOfTarget = new TypeToken<MultiParameters<Integer, Float, Double, String,
BagOfPrimitives>>() {}.getType();
Expand All @@ -93,6 +103,7 @@ public void testTypesWithMultipleParametersDeserialization() throws Exception {
assertEquals(expected, target);
}

@Test
public void testParameterizedTypeWithCustomSerializer() {
Type ptIntegerType = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
Type ptStringType = new TypeToken<MyParameterizedType<String>>() {}.getType();
Expand All @@ -109,6 +120,7 @@ public void testParameterizedTypeWithCustomSerializer() {
assertEquals(MyParameterizedTypeAdapter.<String>getExpectedJson(stringTarget), json);
}

@Test
public void testParameterizedTypesWithCustomDeserializer() {
Type ptIntegerType = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
Type ptStringType = new TypeToken<MyParameterizedType<String>>() {}.getType();
Expand All @@ -130,6 +142,7 @@ public void testParameterizedTypesWithCustomDeserializer() {
assertEquals("abc", stringTarget.value);
}

@Test
public void testParameterizedTypesWithWriterSerialization() throws Exception {
Writer writer = new StringWriter();
MyParameterizedType<Integer> src = new MyParameterizedType<>(10);
Expand All @@ -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<BagOfPrimitives> expected = new MyParameterizedType<>(bag);
Expand All @@ -158,6 +172,7 @@ private static <T> T[] arrayOf(T... args) {
return args;
}

@Test
public void testVariableTypeFieldsAndGenericArraysSerialization() throws Exception {
Integer obj = 0;
Integer[] array = { 1, 2, 3 };
Expand All @@ -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 };
Expand All @@ -191,6 +207,7 @@ public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Excep
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}

@Test
public void testVariableTypeDeserialization() throws Exception {
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
ObjectWithTypeVariables<Integer> objToSerialize =
Expand All @@ -201,6 +218,7 @@ public void testVariableTypeDeserialization() throws Exception {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}

@Test
public void testVariableTypeArrayDeserialization() throws Exception {
Integer[] array = { 1, 2, 3 };

Expand All @@ -213,6 +231,7 @@ public void testVariableTypeArrayDeserialization() throws Exception {
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}

@Test
public void testParameterizedTypeWithVariableTypeDeserialization() throws Exception {
List<Integer> list = new ArrayList<>();
list.add(4);
Expand All @@ -227,6 +246,7 @@ public void testParameterizedTypeWithVariableTypeDeserialization() throws Except
assertEquals(objAfterDeserialization.getExpectedJson(), json);
}

@Test
public void testParameterizedTypeGenericArraysSerialization() throws Exception {
List<Integer> list = new ArrayList<>();
list.add(1);
Expand All @@ -240,6 +260,7 @@ public void testParameterizedTypeGenericArraysSerialization() throws Exception {
assertEquals("{\"arrayOfListOfTypeParameters\":[[1,2],[1,2]]}", json);
}

@Test
public void testParameterizedTypeGenericArraysDeserialization() throws Exception {
List<Integer> list = new ArrayList<>();
list.add(1);
Expand Down Expand Up @@ -483,18 +504,63 @@ public static final class Amount<Q extends Quantity>
int value = 30;
}

@Test
public void testDeepParameterizedTypeSerialization() {
Amount<MyQuantity> amount = new Amount<>();
String json = gson.toJson(amount);
assertTrue(json.contains("value"));
assertTrue(json.contains("30"));
}

@Test
public void testDeepParameterizedTypeDeserialization() {
String json = "{value:30}";
Type type = new TypeToken<Amount<MyQuantity>>() {}.getType();
Amount<MyQuantity> amount = gson.fromJson(json, type);
assertEquals(30, amount.value);
}
// End: tests to reproduce issue 103

private static void assertCorrectlyDeserialized(Object object) {
@SuppressWarnings("unchecked")
List<Quantity> list = (List<Quantity>) object;
assertEquals(1, list.size());
assertEquals(4, list.get(0).q);
}

@Test
public void testGsonFromJsonTypeToken() {
TypeToken<List<Quantity>> typeToken = new TypeToken<List<Quantity>>() {};
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));
}
}
}
Expand Up @@ -33,14 +33,15 @@
*/
public class CollectionsDeserializationBenchmark {

private static final Type LIST_TYPE = new TypeToken<List<BagOfPrimitives>>(){}.getType();
private static final TypeToken<List<BagOfPrimitives>> LIST_TYPE_TOKEN = new TypeToken<List<BagOfPrimitives>>(){};
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();
Expand All @@ -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<reps; ++i) {
gson.fromJson(json, LIST_TYPE);
gson.fromJson(json, LIST_TYPE_TOKEN);
}
}

Expand Down
Expand Up @@ -63,11 +63,11 @@ private enum Document {
READER_SHORT(new TypeToken<Feed>() {}, new TypeReference<Feed>() {}),
READER_LONG(new TypeToken<Feed>() {}, new TypeReference<Feed>() {});

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;
}
}
Expand Down

0 comments on commit 6b9db2e

Please sign in to comment.