Skip to content

Commit b2c00a3

Browse files
authored
Merge pull request google#873 from google/jwilson.0601.get_delegate_adapter
Add support for JsonSerializer/JsonDeserializer in the JsonAdapter annotation
2 parents c24af30 + 1f859ec commit b2c00a3

File tree

6 files changed

+242
-29
lines changed

6 files changed

+242
-29
lines changed

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public final class Gson {
134134
private final boolean generateNonExecutableJson;
135135
private final boolean prettyPrinting;
136136
private final boolean lenient;
137+
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
137138

138139
/**
139140
* Constructs a Gson object with default configuration. The default configuration has the
@@ -245,10 +246,11 @@ public Gson() {
245246
// type adapters for composite and user-defined types
246247
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
247248
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
248-
factories.add(new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor));
249+
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
250+
factories.add(jsonAdapterFactory);
249251
factories.add(TypeAdapters.ENUM_FACTORY);
250252
factories.add(new ReflectiveTypeAdapterFactory(
251-
constructorConstructor, fieldNamingStrategy, excluder));
253+
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
252254

253255
this.factories = Collections.unmodifiableList(factories);
254256
}
@@ -486,12 +488,13 @@ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
486488
* @since 2.2
487489
*/
488490
public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
489-
boolean skipPastFound = false;
490-
// Skip past if and only if the specified factory is present in the factories.
491-
// This is useful because the factories created through JsonAdapter annotations are not
492-
// registered in this list.
493-
if (!factories.contains(skipPast)) skipPastFound = true;
491+
// Hack. If the skipPast factory isn't registered, assume the factory is being requested via
492+
// our @JsonAdapter annotation.
493+
if (!factories.contains(skipPast)) {
494+
skipPast = jsonAdapterFactory;
495+
}
494496

497+
boolean skipPastFound = false;
495498
for (TypeAdapterFactory factory : factories) {
496499
if (!skipPastFound) {
497500
if (factory == skipPast) {

gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.google.gson.internal.bind;
1818

1919
import com.google.gson.Gson;
20+
import com.google.gson.JsonDeserializer;
21+
import com.google.gson.JsonSerializer;
2022
import com.google.gson.TypeAdapter;
2123
import com.google.gson.TypeAdapterFactory;
2224
import com.google.gson.annotations.JsonAdapter;
@@ -30,7 +32,6 @@
3032
* @since 2.3
3133
*/
3234
public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
33-
3435
private final ConstructorConstructor constructorConstructor;
3536

3637
public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
@@ -40,33 +41,42 @@ public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructo
4041
@SuppressWarnings("unchecked")
4142
@Override
4243
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
43-
JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class);
44+
Class<? super T> rawType = targetType.getRawType();
45+
JsonAdapter annotation = rawType.getAnnotation(JsonAdapter.class);
4446
if (annotation == null) {
4547
return null;
4648
}
4749
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
4850
}
4951

50-
@SuppressWarnings("unchecked") // Casts guarded by conditionals.
51-
static TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
52-
TypeToken<?> fieldType, JsonAdapter annotation) {
53-
Class<?> value = annotation.value();
52+
@SuppressWarnings({ "unchecked", "rawtypes" }) // Casts guarded by conditionals.
53+
TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
54+
TypeToken<?> type, JsonAdapter annotation) {
55+
Object instance = constructorConstructor.get(TypeToken.get(annotation.value())).construct();
56+
5457
TypeAdapter<?> typeAdapter;
55-
if (TypeAdapter.class.isAssignableFrom(value)) {
56-
Class<TypeAdapter<?>> typeAdapterClass = (Class<TypeAdapter<?>>) value;
57-
typeAdapter = constructorConstructor.get(TypeToken.get(typeAdapterClass)).construct();
58-
} else if (TypeAdapterFactory.class.isAssignableFrom(value)) {
59-
Class<TypeAdapterFactory> typeAdapterFactory = (Class<TypeAdapterFactory>) value;
60-
typeAdapter = constructorConstructor.get(TypeToken.get(typeAdapterFactory))
61-
.construct()
62-
.create(gson, fieldType);
58+
if (instance instanceof TypeAdapter) {
59+
typeAdapter = (TypeAdapter<?>) instance;
60+
} else if (instance instanceof TypeAdapterFactory) {
61+
typeAdapter = ((TypeAdapterFactory) instance).create(gson, type);
62+
} else if (instance instanceof JsonSerializer || instance instanceof JsonDeserializer) {
63+
JsonSerializer<?> serializer = instance instanceof JsonSerializer
64+
? (JsonSerializer) instance
65+
: null;
66+
JsonDeserializer<?> deserializer = instance instanceof JsonDeserializer
67+
? (JsonDeserializer) instance
68+
: null;
69+
typeAdapter = new TreeTypeAdapter(serializer, deserializer, gson, type, null);
6370
} else {
6471
throw new IllegalArgumentException(
65-
"@JsonAdapter value must be TypeAdapter or TypeAdapterFactory reference.");
72+
"@JsonAdapter value must be TypeAdapter, TypeAdapterFactory, "
73+
+ "JsonSerializer or JsonDeserializer reference.");
6674
}
75+
6776
if (typeAdapter != null) {
6877
typeAdapter = typeAdapter.nullSafe();
6978
}
79+
7080
return typeAdapter;
7181
}
7282
}

gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import com.google.gson.stream.JsonReader;
3333
import com.google.gson.stream.JsonToken;
3434
import com.google.gson.stream.JsonWriter;
35-
3635
import java.io.IOException;
3736
import java.lang.reflect.Field;
3837
import java.lang.reflect.Type;
@@ -42,21 +41,22 @@
4241
import java.util.List;
4342
import java.util.Map;
4443

45-
import static com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter;
46-
4744
/**
4845
* Type adapter that reflects over the fields and methods of a class.
4946
*/
5047
public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
5148
private final ConstructorConstructor constructorConstructor;
5249
private final FieldNamingStrategy fieldNamingPolicy;
5350
private final Excluder excluder;
51+
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
5452

5553
public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
56-
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
54+
FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
55+
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) {
5756
this.constructorConstructor = constructorConstructor;
5857
this.fieldNamingPolicy = fieldNamingPolicy;
5958
this.excluder = excluder;
59+
this.jsonAdapterFactory = jsonAdapterFactory;
6060
}
6161

6262
public boolean excludeField(Field f, boolean serialize) {
@@ -108,7 +108,8 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
108108
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
109109
TypeAdapter<?> mapped = null;
110110
if (annotation != null) {
111-
mapped = getTypeAdapter(constructorConstructor, context, fieldType, annotation);
111+
mapped = jsonAdapterFactory.getTypeAdapter(
112+
constructorConstructor, context, fieldType, annotation);
112113
}
113114
final boolean jsonAdapterPresent = mapped != null;
114115
if (mapped == null) mapped = context.getAdapter(fieldType);

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package com.google.gson.functional;
1818

19+
import java.io.IOException;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
1923
import com.google.gson.Gson;
2024
import com.google.gson.GsonBuilder;
2125
import com.google.gson.TypeAdapter;
@@ -24,7 +28,7 @@
2428
import com.google.gson.reflect.TypeToken;
2529
import com.google.gson.stream.JsonReader;
2630
import com.google.gson.stream.JsonWriter;
27-
import java.io.IOException;
31+
2832
import junit.framework.TestCase;
2933

3034
/**
@@ -268,4 +272,35 @@ private static final class LongToStringTypeAdapterFactory implements TypeAdapter
268272
+ " annotated with @JsonAdapter(LongToStringTypeAdapterFactory.class)");
269273
}
270274
}
275+
276+
public void testFieldAnnotationWorksForParameterizedType() {
277+
Gson gson = new Gson();
278+
String json = gson.toJson(new Gizmo2(Arrays.asList(new Part("Part"))));
279+
assertEquals("{\"part\":\"GizmoPartTypeAdapterFactory\"}", json);
280+
Gizmo2 computer = gson.fromJson("{'part':'Part'}", Gizmo2.class);
281+
assertEquals("GizmoPartTypeAdapterFactory", computer.part.get(0).name);
282+
}
283+
284+
private static final class Gizmo2 {
285+
@JsonAdapter(Gizmo2PartTypeAdapterFactory.class)
286+
List<Part> part;
287+
Gizmo2(List<Part> part) {
288+
this.part = part;
289+
}
290+
}
291+
292+
private static class Gizmo2PartTypeAdapterFactory implements TypeAdapterFactory {
293+
@Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
294+
return new TypeAdapter<T>() {
295+
@Override public void write(JsonWriter out, T value) throws IOException {
296+
out.value("GizmoPartTypeAdapterFactory");
297+
}
298+
@SuppressWarnings("unchecked")
299+
@Override public T read(JsonReader in) throws IOException {
300+
in.nextString();
301+
return (T) Arrays.asList(new Part("GizmoPartTypeAdapterFactory"));
302+
}
303+
};
304+
}
305+
}
271306
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (C) 2016 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.gson.functional;
18+
19+
import java.lang.reflect.Type;
20+
21+
import com.google.gson.Gson;
22+
import com.google.gson.JsonDeserializationContext;
23+
import com.google.gson.JsonDeserializer;
24+
import com.google.gson.JsonElement;
25+
import com.google.gson.JsonParseException;
26+
import com.google.gson.JsonPrimitive;
27+
import com.google.gson.JsonSerializationContext;
28+
import com.google.gson.JsonSerializer;
29+
import com.google.gson.annotations.JsonAdapter;
30+
31+
import junit.framework.TestCase;
32+
33+
/**
34+
* Functional tests for the {@link JsonAdapter} annotation on fields where the value is of
35+
* type {@link JsonSerializer} or {@link JsonDeserializer}.
36+
*/
37+
public final class JsonAdapterSerializerDeserializerTest extends TestCase {
38+
39+
public void testJsonSerializerDeserializerBasedJsonAdapterOnFields() {
40+
Gson gson = new Gson();
41+
String json = gson.toJson(new Computer(new User("Inderjeet Singh"), null, new User("Jesse Wilson")));
42+
assertEquals("{\"user1\":\"UserSerializer\",\"user3\":\"UserSerializerDeserializer\"}", json);
43+
Computer computer = gson.fromJson("{'user2':'Jesse Wilson','user3':'Jake Wharton'}", Computer.class);
44+
assertEquals("UserSerializer", computer.user2.name);
45+
assertEquals("UserSerializerDeserializer", computer.user3.name);
46+
}
47+
48+
private static final class Computer {
49+
@JsonAdapter(UserSerializer.class) final User user1;
50+
@JsonAdapter(UserDeserializer.class) final User user2;
51+
@JsonAdapter(UserSerializerDeserializer.class) final User user3;
52+
Computer(User user1, User user2, User user3) {
53+
this.user1 = user1;
54+
this.user2 = user2;
55+
this.user3 = user3;
56+
}
57+
}
58+
59+
private static final class User {
60+
public final String name;
61+
private User(String name) {
62+
this.name = name;
63+
}
64+
}
65+
66+
private static final class UserSerializer implements JsonSerializer<User> {
67+
@Override
68+
public JsonElement serialize(User src, Type typeOfSrc, JsonSerializationContext context) {
69+
return new JsonPrimitive("UserSerializer");
70+
}
71+
}
72+
73+
private static final class UserDeserializer implements JsonDeserializer<User> {
74+
@Override
75+
public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
76+
throws JsonParseException {
77+
return new User("UserSerializer");
78+
}
79+
}
80+
81+
private static final class UserSerializerDeserializer implements JsonSerializer<User>, JsonDeserializer<User> {
82+
@Override
83+
public JsonElement serialize(User src, Type typeOfSrc, JsonSerializationContext context) {
84+
return new JsonPrimitive("UserSerializerDeserializer");
85+
}
86+
@Override
87+
public User deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
88+
throws JsonParseException {
89+
return new User("UserSerializerDeserializer");
90+
}
91+
}
92+
93+
public void testJsonSerializerDeserializerBasedJsonAdapterOnClass() {
94+
Gson gson = new Gson();
95+
String json = gson.toJson(new Computer2(new User2("Inderjeet Singh")));
96+
assertEquals("{\"user\":\"UserSerializerDeserializer2\"}", json);
97+
Computer2 computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer2.class);
98+
assertEquals("UserSerializerDeserializer2", computer.user.name);
99+
}
100+
101+
private static final class Computer2 {
102+
final User2 user;
103+
Computer2(User2 user) {
104+
this.user = user;
105+
}
106+
}
107+
108+
@JsonAdapter(UserSerializerDeserializer2.class)
109+
private static final class User2 {
110+
public final String name;
111+
private User2(String name) {
112+
this.name = name;
113+
}
114+
}
115+
116+
private static final class UserSerializerDeserializer2 implements JsonSerializer<User2>, JsonDeserializer<User2> {
117+
@Override
118+
public JsonElement serialize(User2 src, Type typeOfSrc, JsonSerializationContext context) {
119+
return new JsonPrimitive("UserSerializerDeserializer2");
120+
}
121+
@Override
122+
public User2 deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
123+
throws JsonParseException {
124+
return new User2("UserSerializerDeserializer2");
125+
}
126+
}
127+
128+
public void testDifferentJsonAdaptersForGenericFieldsOfSameRawType() {
129+
Container c = new Container("Foo", 10);
130+
Gson gson = new Gson();
131+
String json = gson.toJson(c);
132+
assertTrue(json.contains("\"a\":\"BaseStringAdapter\""));
133+
assertTrue(json.contains("\"b\":\"BaseIntegerAdapter\""));
134+
}
135+
136+
private static final class Container {
137+
@JsonAdapter(BaseStringAdapter.class) Base<String> a;
138+
@JsonAdapter(BaseIntegerAdapter.class) Base<Integer> b;
139+
Container(String a, int b) {
140+
this.a = new Base<String>(a);
141+
this.b = new Base<Integer>(b);
142+
}
143+
}
144+
145+
private static final class Base<T> {
146+
@SuppressWarnings("unused")
147+
T value;
148+
Base(T value) {
149+
this.value = value;
150+
}
151+
}
152+
153+
private static final class BaseStringAdapter implements JsonSerializer<Base<String>> {
154+
@Override public JsonElement serialize(Base<String> src, Type typeOfSrc, JsonSerializationContext context) {
155+
return new JsonPrimitive("BaseStringAdapter");
156+
}
157+
}
158+
159+
private static final class BaseIntegerAdapter implements JsonSerializer<Base<Integer>> {
160+
@Override public JsonElement serialize(Base<Integer> src, Type typeOfSrc, JsonSerializationContext context) {
161+
return new JsonPrimitive("BaseIntegerAdapter");
162+
}
163+
}
164+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public final class RuntimeTypeAdapterFactoryFunctionalTest extends TestCase {
4545
* This test also ensures that {@link TypeAdapterFactory} registered through {@link JsonAdapter}
4646
* work correctly for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)}.
4747
*/
48-
public void testSubclassesAutomaticallySerialzed() throws Exception {
48+
public void testSubclassesAutomaticallySerialized() throws Exception {
4949
Shape shape = new Circle(25);
5050
String json = gson.toJson(shape);
5151
shape = gson.fromJson(json, Shape.class);

0 commit comments

Comments
 (0)