Skip to content

Commit 2deb209

Browse files
Make RuntimeTypeAdapterFactory recognize subclasses only conditionally. (#2160)
PR #2139 changed this factory so that if given a certain baseType, it will also recognize any subtype of that type. That is often the right thing to do, but it is a change in behaviour, and does in fact break at least one current client of this code. So instead we introduce a new `recognizeSubclasses()` method that triggers this behaviour. When the method is not called, we revert to the old behaviour of only recognizing instances of the exact class `baseType`.
1 parent 924c496 commit 2deb209

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.util.LinkedHashMap;
3131
import java.util.Map;
3232

33-
3433
/**
3534
* Adapts values whose runtime type may differ from their declaration type. This
3635
* is necessary when a field's type is not the same type that GSON should create
@@ -138,8 +137,10 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
138137
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
139138
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
140139
private final boolean maintainType;
140+
private boolean recognizeSubtypes;
141141

142-
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
142+
private RuntimeTypeAdapterFactory(
143+
Class<?> baseType, String typeFieldName, boolean maintainType) {
143144
if (typeFieldName == null || baseType == null) {
144145
throw new NullPointerException();
145146
}
@@ -151,7 +152,8 @@ private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boole
151152
/**
152153
* Creates a new runtime type adapter using for {@code baseType} using {@code
153154
* typeFieldName} as the type field name. Type field names are case sensitive.
154-
* {@code maintainType} flag decide if the type will be stored in pojo or not.
155+
*
156+
* @param maintainType true if the type field should be included in deserialized objects
155157
*/
156158
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
157159
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
@@ -173,6 +175,15 @@ public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
173175
return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
174176
}
175177

178+
/**
179+
* Ensures that this factory will handle not just the given {@code baseType}, but any subtype
180+
* of that type.
181+
*/
182+
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
183+
this.recognizeSubtypes = true;
184+
return this;
185+
}
186+
176187
/**
177188
* Registers {@code type} identified by {@code label}. Labels are case
178189
* sensitive.
@@ -205,7 +216,13 @@ public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
205216

206217
@Override
207218
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
208-
if (type == null || !baseType.isAssignableFrom(type.getRawType())) {
219+
if (type == null) {
220+
return null;
221+
}
222+
Class<?> rawType = type.getRawType();
223+
boolean handle =
224+
recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
225+
if (!handle) {
209226
return null;
210227
}
211228

extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,27 @@ public void testRuntimeTypeAdapter() {
3434

3535
CreditCard original = new CreditCard("Jesse", 234);
3636
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
37-
//do not give the explicit typeOfSrc, because if this would be in a list
38-
//or an attribute, there would also be no hint. See #712
37+
gson.toJson(original, BillingInstrument.class));
38+
BillingInstrument deserialized = gson.fromJson(
39+
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
40+
assertEquals("Jesse", deserialized.ownerName);
41+
assertTrue(deserialized instanceof CreditCard);
42+
}
43+
44+
public void testRuntimeTypeAdapterRecognizeSubtypes() {
45+
// We don't have an explicit factory for CreditCard.class, but we do have one for
46+
// BillingInstrument.class that has recognizeSubtypes(). So it should recognize CreditCard, and
47+
// when we call gson.toJson(original) below, without an explicit type, it should be invoked.
48+
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
49+
BillingInstrument.class)
50+
.recognizeSubtypes()
51+
.registerSubtype(CreditCard.class);
52+
Gson gson = new GsonBuilder()
53+
.registerTypeAdapterFactory(rta)
54+
.create();
55+
56+
CreditCard original = new CreditCard("Jesse", 234);
57+
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
3958
gson.toJson(original));
4059
BillingInstrument deserialized = gson.fromJson(
4160
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);

0 commit comments

Comments
 (0)