Skip to content

Commit 6b9db2e

Browse files
authored
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.
1 parent a733150 commit 6b9db2e

File tree

7 files changed

+364
-111
lines changed

7 files changed

+364
-111
lines changed

UserGuide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ Collection<Integer> ints = Arrays.asList(1,2,3,4,5);
225225
String json = gson.toJson(ints); // ==> json is [1,2,3,4,5]
226226

227227
// Deserialization
228-
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
228+
TypeToken<Collection<Integer>> collectionType = new TypeToken<Collection<Integer>>(){};
229229
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
230230
// ==> ints2 is same as ints
231231
```
@@ -263,7 +263,7 @@ For deserialization Gson uses the `read` method of the `TypeAdapter` registered
263263

264264
```java
265265
Gson gson = new Gson();
266-
Type mapType = new TypeToken<Map<String, String>>(){}.getType();
266+
TypeToken<Map<String, String>> mapType = new TypeToken<Map<String, String>>(){};
267267
String json = "{\"key\": \"value\"}";
268268

269269
// Deserialization

gson/src/main/java/com/google/gson/Gson.java

Lines changed: 266 additions & 94 deletions
Large diffs are not rendered by default.

gson/src/main/java/com/google/gson/reflect/TypeToken.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* runtime.
3333
*
3434
* <p>For example, to create a type literal for {@code List<String>}, you can
35-
* create an empty anonymous inner class:
35+
* create an empty anonymous class:
3636
*
3737
* <p>
3838
* {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
@@ -43,6 +43,11 @@
4343
* might expect, which gives a false sense of type-safety at compilation time
4444
* and can lead to an unexpected {@code ClassCastException} at runtime.
4545
*
46+
* <p>If the type arguments of the parameterized type are only available at
47+
* runtime, for example when you want to create a {@code List<E>} based on
48+
* a {@code Class<E>} representing the element type, the method
49+
* {@link #getParameterized(Type, Type...)} can be used.
50+
*
4651
* @author Bob Lee
4752
* @author Sven Mawson
4853
* @author Jesse Wilson
@@ -317,8 +322,17 @@ public static <T> TypeToken<T> get(Class<T> type) {
317322
}
318323

319324
/**
320-
* Gets type literal for the parameterized type represented by applying {@code typeArguments} to
321-
* {@code rawType}.
325+
* Gets a type literal for the parameterized type represented by applying {@code typeArguments} to
326+
* {@code rawType}. This is mainly intended for situations where the type arguments are not
327+
* available at compile time. The following example shows how a type token for {@code Map<K, V>}
328+
* can be created:
329+
* <pre>{@code
330+
* Class<K> keyClass = ...;
331+
* Class<V> valueClass = ...;
332+
* TypeToken<?> mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass);
333+
* }</pre>
334+
* As seen here the result is a {@code TypeToken<?>}; this method cannot provide any type safety,
335+
* and care must be taken to pass in the correct number of type arguments.
322336
*
323337
* @throws IllegalArgumentException
324338
* If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for

gson/src/test/java/com/google/gson/MixedStreamTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ public void testReadNulls() {
174174
} catch (NullPointerException expected) {
175175
}
176176
try {
177-
gson.fromJson(new JsonReader(new StringReader("true")), null);
177+
gson.fromJson(new JsonReader(new StringReader("true")), (Type) null);
178178
fail();
179179
} catch (NullPointerException expected) {
180180
}

gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@
1616

1717
package com.google.gson.functional;
1818

19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertTrue;
21+
1922
import com.google.gson.Gson;
2023
import com.google.gson.GsonBuilder;
24+
import com.google.gson.JsonArray;
25+
import com.google.gson.JsonObject;
2126
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedType;
2227
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeAdapter;
2328
import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeInstanceCreator;
2429
import com.google.gson.common.TestTypes.BagOfPrimitives;
2530
import com.google.gson.reflect.TypeToken;
31+
import com.google.gson.stream.JsonReader;
2632
import java.io.Reader;
2733
import java.io.Serializable;
2834
import java.io.StringReader;
@@ -32,30 +38,32 @@
3238
import java.util.ArrayList;
3339
import java.util.Arrays;
3440
import java.util.List;
35-
import junit.framework.TestCase;
41+
import org.junit.Before;
42+
import org.junit.Test;
3643

3744
/**
3845
* Functional tests for the serialization and deserialization of parameterized types in Gson.
3946
*
4047
* @author Inderjeet Singh
4148
* @author Joel Leitch
4249
*/
43-
public class ParameterizedTypesTest extends TestCase {
50+
public class ParameterizedTypesTest {
4451
private Gson gson;
4552

46-
@Override
47-
protected void setUp() throws Exception {
48-
super.setUp();
53+
@Before
54+
public void setUp() {
4955
gson = new Gson();
5056
}
5157

58+
@Test
5259
public void testParameterizedTypesSerialization() throws Exception {
5360
MyParameterizedType<Integer> src = new MyParameterizedType<>(10);
5461
Type typeOfSrc = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
5562
String json = gson.toJson(src, typeOfSrc);
5663
assertEquals(src.getExpectedJson(), json);
5764
}
5865

66+
@Test
5967
public void testParameterizedTypeDeserialization() throws Exception {
6068
BagOfPrimitives bag = new BagOfPrimitives();
6169
MyParameterizedType<BagOfPrimitives> expected = new MyParameterizedType<>(bag);
@@ -70,6 +78,7 @@ public void testParameterizedTypeDeserialization() throws Exception {
7078
assertEquals(expected, actual);
7179
}
7280

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

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

106+
@Test
96107
public void testParameterizedTypeWithCustomSerializer() {
97108
Type ptIntegerType = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
98109
Type ptStringType = new TypeToken<MyParameterizedType<String>>() {}.getType();
@@ -109,6 +120,7 @@ public void testParameterizedTypeWithCustomSerializer() {
109120
assertEquals(MyParameterizedTypeAdapter.<String>getExpectedJson(stringTarget), json);
110121
}
111122

123+
@Test
112124
public void testParameterizedTypesWithCustomDeserializer() {
113125
Type ptIntegerType = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
114126
Type ptStringType = new TypeToken<MyParameterizedType<String>>() {}.getType();
@@ -130,6 +142,7 @@ public void testParameterizedTypesWithCustomDeserializer() {
130142
assertEquals("abc", stringTarget.value);
131143
}
132144

145+
@Test
133146
public void testParameterizedTypesWithWriterSerialization() throws Exception {
134147
Writer writer = new StringWriter();
135148
MyParameterizedType<Integer> src = new MyParameterizedType<>(10);
@@ -138,6 +151,7 @@ public void testParameterizedTypesWithWriterSerialization() throws Exception {
138151
assertEquals(src.getExpectedJson(), writer.toString());
139152
}
140153

154+
@Test
141155
public void testParameterizedTypeWithReaderDeserialization() throws Exception {
142156
BagOfPrimitives bag = new BagOfPrimitives();
143157
MyParameterizedType<BagOfPrimitives> expected = new MyParameterizedType<>(bag);
@@ -158,6 +172,7 @@ private static <T> T[] arrayOf(T... args) {
158172
return args;
159173
}
160174

175+
@Test
161176
public void testVariableTypeFieldsAndGenericArraysSerialization() throws Exception {
162177
Integer obj = 0;
163178
Integer[] array = { 1, 2, 3 };
@@ -174,6 +189,7 @@ public void testVariableTypeFieldsAndGenericArraysSerialization() throws Excepti
174189
assertEquals(objToSerialize.getExpectedJson(), json);
175190
}
176191

192+
@Test
177193
public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Exception {
178194
Integer obj = 0;
179195
Integer[] array = { 1, 2, 3 };
@@ -191,6 +207,7 @@ public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Excep
191207
assertEquals(objAfterDeserialization.getExpectedJson(), json);
192208
}
193209

210+
@Test
194211
public void testVariableTypeDeserialization() throws Exception {
195212
Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
196213
ObjectWithTypeVariables<Integer> objToSerialize =
@@ -201,6 +218,7 @@ public void testVariableTypeDeserialization() throws Exception {
201218
assertEquals(objAfterDeserialization.getExpectedJson(), json);
202219
}
203220

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

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

234+
@Test
216235
public void testParameterizedTypeWithVariableTypeDeserialization() throws Exception {
217236
List<Integer> list = new ArrayList<>();
218237
list.add(4);
@@ -227,6 +246,7 @@ public void testParameterizedTypeWithVariableTypeDeserialization() throws Except
227246
assertEquals(objAfterDeserialization.getExpectedJson(), json);
228247
}
229248

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

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

507+
@Test
486508
public void testDeepParameterizedTypeSerialization() {
487509
Amount<MyQuantity> amount = new Amount<>();
488510
String json = gson.toJson(amount);
489511
assertTrue(json.contains("value"));
490512
assertTrue(json.contains("30"));
491513
}
492514

515+
@Test
493516
public void testDeepParameterizedTypeDeserialization() {
494517
String json = "{value:30}";
495518
Type type = new TypeToken<Amount<MyQuantity>>() {}.getType();
496519
Amount<MyQuantity> amount = gson.fromJson(json, type);
497520
assertEquals(30, amount.value);
498521
}
499522
// End: tests to reproduce issue 103
523+
524+
private static void assertCorrectlyDeserialized(Object object) {
525+
@SuppressWarnings("unchecked")
526+
List<Quantity> list = (List<Quantity>) object;
527+
assertEquals(1, list.size());
528+
assertEquals(4, list.get(0).q);
529+
}
530+
531+
@Test
532+
public void testGsonFromJsonTypeToken() {
533+
TypeToken<List<Quantity>> typeToken = new TypeToken<List<Quantity>>() {};
534+
Type type = typeToken.getType();
535+
536+
{
537+
JsonObject jsonObject = new JsonObject();
538+
jsonObject.addProperty("q", 4);
539+
JsonArray jsonArray = new JsonArray();
540+
jsonArray.add(jsonObject);
541+
542+
assertCorrectlyDeserialized(gson.fromJson(jsonArray, typeToken));
543+
assertCorrectlyDeserialized(gson.fromJson(jsonArray, type));
544+
}
545+
546+
String json = "[{\"q\":4}]";
547+
548+
{
549+
assertCorrectlyDeserialized(gson.fromJson(json, typeToken));
550+
assertCorrectlyDeserialized(gson.fromJson(json, type));
551+
}
552+
553+
{
554+
assertCorrectlyDeserialized(gson.fromJson(new StringReader(json), typeToken));
555+
assertCorrectlyDeserialized(gson.fromJson(new StringReader(json), type));
556+
}
557+
558+
{
559+
JsonReader reader = new JsonReader(new StringReader(json));
560+
assertCorrectlyDeserialized(gson.fromJson(reader, typeToken));
561+
562+
reader = new JsonReader(new StringReader(json));
563+
assertCorrectlyDeserialized(gson.fromJson(reader, type));
564+
}
565+
}
500566
}

metrics/src/main/java/com/google/gson/metrics/CollectionsDeserializationBenchmark.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@
3333
*/
3434
public class CollectionsDeserializationBenchmark {
3535

36-
private static final Type LIST_TYPE = new TypeToken<List<BagOfPrimitives>>(){}.getType();
36+
private static final TypeToken<List<BagOfPrimitives>> LIST_TYPE_TOKEN = new TypeToken<List<BagOfPrimitives>>(){};
37+
private static final Type LIST_TYPE = LIST_TYPE_TOKEN.getType();
3738
private Gson gson;
3839
private String json;
3940

4041
public static void main(String[] args) {
4142
NonUploadingCaliperRunner.run(CollectionsDeserializationBenchmark.class, args);
4243
}
43-
44+
4445
@BeforeExperiment
4546
void setUp() throws Exception {
4647
this.gson = new Gson();
@@ -51,12 +52,12 @@ void setUp() throws Exception {
5152
this.json = gson.toJson(bags, LIST_TYPE);
5253
}
5354

54-
/**
55+
/**
5556
* Benchmark to measure Gson performance for deserializing an object
5657
*/
5758
public void timeCollectionsDefault(int reps) {
5859
for (int i=0; i<reps; ++i) {
59-
gson.fromJson(json, LIST_TYPE);
60+
gson.fromJson(json, LIST_TYPE_TOKEN);
6061
}
6162
}
6263

metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ private enum Document {
6363
READER_SHORT(new TypeToken<Feed>() {}, new TypeReference<Feed>() {}),
6464
READER_LONG(new TypeToken<Feed>() {}, new TypeReference<Feed>() {});
6565

66-
private final Type gsonType;
66+
private final TypeToken<?> gsonType;
6767
private final TypeReference<?> jacksonType;
6868

6969
private Document(TypeToken<?> typeToken, TypeReference<?> typeReference) {
70-
this.gsonType = typeToken.getType();
70+
this.gsonType = typeToken;
7171
this.jacksonType = typeReference;
7272
}
7373
}

0 commit comments

Comments
 (0)