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`.
This commit is contained in:
parent
924c496b95
commit
2deb2099d3
@ -30,7 +30,6 @@ import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Adapts values whose runtime type may differ from their declaration type. This
|
||||
* 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 {
|
||||
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
|
||||
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
|
||||
private final boolean maintainType;
|
||||
private boolean recognizeSubtypes;
|
||||
|
||||
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName, boolean maintainType) {
|
||||
private RuntimeTypeAdapterFactory(
|
||||
Class<?> baseType, String typeFieldName, boolean maintainType) {
|
||||
if (typeFieldName == null || baseType == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
@ -151,7 +152,8 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
/**
|
||||
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||
* typeFieldName} as the type field name. Type field names are case sensitive.
|
||||
* {@code maintainType} flag decide if the type will be stored in pojo or not.
|
||||
*
|
||||
* @param maintainType true if the type field should be included in deserialized objects
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
|
||||
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
|
||||
@ -173,6 +175,15 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that this factory will handle not just the given {@code baseType}, but any subtype
|
||||
* of that type.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> recognizeSubtypes() {
|
||||
this.recognizeSubtypes = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by {@code label}. Labels are case
|
||||
* sensitive.
|
||||
@ -205,7 +216,13 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
|
||||
@Override
|
||||
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
|
||||
if (type == null || !baseType.isAssignableFrom(type.getRawType())) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?> rawType = type.getRawType();
|
||||
boolean handle =
|
||||
recognizeSubtypes ? baseType.isAssignableFrom(rawType) : baseType.equals(rawType);
|
||||
if (!handle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,27 @@ public final class RuntimeTypeAdapterFactoryTest extends TestCase {
|
||||
|
||||
CreditCard original = new CreditCard("Jesse", 234);
|
||||
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
|
||||
//do not give the explicit typeOfSrc, because if this would be in a list
|
||||
//or an attribute, there would also be no hint. See #712
|
||||
gson.toJson(original, BillingInstrument.class));
|
||||
BillingInstrument deserialized = gson.fromJson(
|
||||
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
|
||||
assertEquals("Jesse", deserialized.ownerName);
|
||||
assertTrue(deserialized instanceof CreditCard);
|
||||
}
|
||||
|
||||
public void testRuntimeTypeAdapterRecognizeSubtypes() {
|
||||
// We don't have an explicit factory for CreditCard.class, but we do have one for
|
||||
// BillingInstrument.class that has recognizeSubtypes(). So it should recognize CreditCard, and
|
||||
// when we call gson.toJson(original) below, without an explicit type, it should be invoked.
|
||||
RuntimeTypeAdapterFactory<BillingInstrument> rta = RuntimeTypeAdapterFactory.of(
|
||||
BillingInstrument.class)
|
||||
.recognizeSubtypes()
|
||||
.registerSubtype(CreditCard.class);
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(rta)
|
||||
.create();
|
||||
|
||||
CreditCard original = new CreditCard("Jesse", 234);
|
||||
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
|
||||
gson.toJson(original));
|
||||
BillingInstrument deserialized = gson.fromJson(
|
||||
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
|
||||
|
Loading…
Reference in New Issue
Block a user