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.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts values whose runtime type may differ from their declaration type. This
|
* 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
|
* 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<String, Class<?>> labelToSubtype = new LinkedHashMap<>();
|
||||||
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
|
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<>();
|
||||||
private final boolean maintainType;
|
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) {
|
if (typeFieldName == null || baseType == null) {
|
||||||
throw new NullPointerException();
|
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
|
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||||
* typeFieldName} as the type field name. Type field names are case sensitive.
|
* 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) {
|
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName, boolean maintainType) {
|
||||||
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
|
return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType);
|
||||||
|
@ -173,6 +175,15 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||||
return new RuntimeTypeAdapterFactory<>(baseType, "type", false);
|
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
|
* Registers {@code type} identified by {@code label}. Labels are case
|
||||||
* sensitive.
|
* sensitive.
|
||||||
|
@ -205,7 +216,13 @@ public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,27 @@ public final class RuntimeTypeAdapterFactoryTest extends TestCase {
|
||||||
|
|
||||||
CreditCard original = new CreditCard("Jesse", 234);
|
CreditCard original = new CreditCard("Jesse", 234);
|
||||||
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
|
assertEquals("{\"type\":\"CreditCard\",\"cvv\":234,\"ownerName\":\"Jesse\"}",
|
||||||
//do not give the explicit typeOfSrc, because if this would be in a list
|
gson.toJson(original, BillingInstrument.class));
|
||||||
//or an attribute, there would also be no hint. See #712
|
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));
|
gson.toJson(original));
|
||||||
BillingInstrument deserialized = gson.fromJson(
|
BillingInstrument deserialized = gson.fromJson(
|
||||||
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
|
"{type:'CreditCard',cvv:234,ownerName:'Jesse'}", BillingInstrument.class);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user