diff --git a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java index a8c6368c..502ad4ec 100644 --- a/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java +++ b/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -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 implements TypeAdapterFactory { private final Map> labelToSubtype = new LinkedHashMap<>(); private final Map, 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 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 RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName, boolean maintainType) { return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); @@ -173,6 +175,15 @@ public final class RuntimeTypeAdapterFactory 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 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 implements TypeAdapterFactory { @Override public TypeAdapter create(Gson gson, TypeToken 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; } diff --git a/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java b/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java index e58ee0f9..5159001c 100644 --- a/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java +++ b/extras/src/test/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactoryTest.java @@ -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 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);