From 89103b6d131cd2a695ebfd5bd1584da06a78d9e6 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Sat, 20 Oct 2018 00:46:06 -0400 Subject: [PATCH] Rename RuntimeJsonAdapterFactory to PolymorphicJsonAdapterFactory Also expand the documentation. --- ...ava => PolymorphicJsonAdapterFactory.java} | 92 +++++++++++++++---- ...=> PolymorphicJsonAdapterFactoryTest.java} | 34 +++---- 2 files changed, 91 insertions(+), 35 deletions(-) rename adapters/src/main/java/com/squareup/moshi/adapters/{RuntimeJsonAdapterFactory.java => PolymorphicJsonAdapterFactory.java} (67%) rename adapters/src/test/java/com/squareup/moshi/adapters/{RuntimeJsonAdapterFactoryTest.java => PolymorphicJsonAdapterFactoryTest.java} (87%) diff --git a/adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java b/adapters/src/main/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactory.java similarity index 67% rename from adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java rename to adapters/src/main/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactory.java index 9c9d41d..de0ca32 100644 --- a/adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java +++ b/adapters/src/main/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactory.java @@ -31,28 +31,84 @@ import java.util.Set; import javax.annotation.CheckReturnValue; /** - * A JsonAdapter factory for polymorphic types. This is useful when the type is not known before - * decoding the JSON. This factory's adapters expect JSON in the format of a JSON object with a - * key whose value is a label that determines the type to which to map the JSON object. To use, add - * this factory to your {@link Moshi.Builder}: + * A JsonAdapter factory for objects that include type information in the JSON. When decoding JSON + * Moshi uses this type information to determine which class to decode to. When encoding Moshi uses + * the object’s class to determine what type information to include. + * + *

Suppose we have an interface, its implementations, and a class that uses them: + * + *

 {@code
+ *
+ *   interface HandOfCards {
+ *   }
+ *
+ *   class BlackjackHand extends HandOfCards {
+ *     Card hidden_card;
+ *     List visible_cards;
+ *   }
+ *
+ *   class HoldemHand extends HandOfCards {
+ *     Set hidden_cards;
+ *   }
+ *
+ *   class Player {
+ *     String name;
+ *     HandOfCards hand;
+ *   }
+ * }
+ * + *

We want to decode the following JSON into the player model above: + * + *

 {@code
+ *
+ *   {
+ *     "name": "Jesse",
+ *     "hand": {
+ *       "hand_type": "blackjack",
+ *       "hidden_card": "9D",
+ *       "visible_cards": ["8H", "4C"]
+ *     }
+ *   }
+ * }
+ * + *

Left unconfigured, Moshi would incorrectly attempt to decode the hand object to the abstract + * {@code HandOfCards} interface. We configure it to use the appropriate subtype instead: * *

 {@code
  *
  *   Moshi moshi = new Moshi.Builder()
- *       .add(RuntimeJsonAdapterFactory.of(Message.class, "type")
- *           .withSubtype(Success.class, "success")
- *           .withSubtype(Error.class, "error"))
+ *       .add(PolymorphicJsonAdapterFactory.of(HandOfCards.class, "hand_type")
+ *           .withSubtype(BlackjackHand.class, "blackjack")
+ *           .withSubtype(HoldemHand.class, "holdem"))
  *       .build();
  * }
+ * + *

This class imposes strict requirements on its use: + * + *

+ * + *

For best performance type information should be the first field in the object. Otherwise Moshi + * must reprocess the JSON stream once it knows the object's type. + * + *

If an unknown subtype is encountered when decoding, this will throw a {@link + * JsonDataException}. If an unknown type is encountered when encoding, this will throw an {@link + * IllegalArgumentException}. */ - -public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { +public final class PolymorphicJsonAdapterFactory implements JsonAdapter.Factory { final Class baseType; final String labelKey; final List labels; final List subtypes; - RuntimeJsonAdapterFactory( + PolymorphicJsonAdapterFactory( Class baseType, String labelKey, List labels, List subtypes) { this.baseType = baseType; this.labelKey = labelKey; @@ -66,14 +122,14 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { * JSON object. */ @CheckReturnValue - public static RuntimeJsonAdapterFactory of(Class baseType, String labelKey) { + public static PolymorphicJsonAdapterFactory of(Class baseType, String labelKey) { if (baseType == null) throw new NullPointerException("baseType == null"); if (labelKey == null) throw new NullPointerException("labelKey == null"); if (baseType == Object.class) { throw new IllegalArgumentException( "The base type must not be Object. Consider using a marker interface."); } - return new RuntimeJsonAdapterFactory<>( + return new PolymorphicJsonAdapterFactory<>( baseType, labelKey, Collections.emptyList(), Collections.emptyList()); } @@ -82,7 +138,7 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { * during encoding an {@linkplain IllegalArgumentException} will be thrown. When an unknown label * is found during decoding a {@linkplain JsonDataException} will be thrown. */ - public RuntimeJsonAdapterFactory withSubtype(Class subtype, String label) { + public PolymorphicJsonAdapterFactory withSubtype(Class subtype, String label) { if (subtype == null) throw new NullPointerException("subtype == null"); if (label == null) throw new NullPointerException("label == null"); if (labels.contains(label) || subtypes.contains(subtype)) { @@ -92,7 +148,7 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { newLabels.add(label); List newSubtypes = new ArrayList<>(subtypes); newSubtypes.add(subtype); - return new RuntimeJsonAdapterFactory<>(baseType, labelKey, newLabels, newSubtypes); + return new PolymorphicJsonAdapterFactory<>(baseType, labelKey, newLabels, newSubtypes); } @Override @@ -107,11 +163,11 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { } JsonAdapter objectJsonAdapter = moshi.adapter(Object.class); - return new RuntimeJsonAdapter( + return new PolymorphicJsonAdapter( labelKey, labels, subtypes, jsonAdapters, objectJsonAdapter).nullSafe(); } - static final class RuntimeJsonAdapter extends JsonAdapter { + static final class PolymorphicJsonAdapter extends JsonAdapter { final String labelKey; final List labels; final List subtypes; @@ -123,7 +179,7 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { /** Corresponds to subtypes. */ final JsonReader.Options labelOptions; - RuntimeJsonAdapter(String labelKey, List labels, + PolymorphicJsonAdapter(String labelKey, List labels, List subtypes, List> jsonAdapters, JsonAdapter objectJsonAdapter) { this.labelKey = labelKey; @@ -189,7 +245,7 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { } @Override public String toString() { - return "RuntimeJsonAdapter(" + labelKey + ")"; + return "PolymorphicJsonAdapter(" + labelKey + ")"; } } } diff --git a/adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java b/adapters/src/test/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactoryTest.java similarity index 87% rename from adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java rename to adapters/src/test/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactoryTest.java index 31b59a2..427057a 100644 --- a/adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java +++ b/adapters/src/test/java/com/squareup/moshi/adapters/PolymorphicJsonAdapterFactoryTest.java @@ -29,10 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @SuppressWarnings("CheckReturnValue") -public final class RuntimeJsonAdapterFactoryTest { +public final class PolymorphicJsonAdapterFactoryTest { @Test public void fromJson() throws IOException { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -46,7 +46,7 @@ public final class RuntimeJsonAdapterFactoryTest { @Test public void toJson() { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -60,7 +60,7 @@ public final class RuntimeJsonAdapterFactoryTest { @Test public void unregisteredLabelValue() throws IOException { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -80,7 +80,7 @@ public final class RuntimeJsonAdapterFactoryTest { @Test public void unregisteredSubtype() { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -90,17 +90,17 @@ public final class RuntimeJsonAdapterFactoryTest { adapter.toJson(new EmptyMessage()); } catch (IllegalArgumentException expected) { assertThat(expected).hasMessage("Expected one of [class" - + " com.squareup.moshi.adapters.RuntimeJsonAdapterFactoryTest$Success, class" - + " com.squareup.moshi.adapters.RuntimeJsonAdapterFactoryTest$Error] but found" + + " com.squareup.moshi.adapters.PolymorphicJsonAdapterFactoryTest$Success, class" + + " com.squareup.moshi.adapters.PolymorphicJsonAdapterFactoryTest$Error] but found" + " EmptyMessage, a class" - + " com.squareup.moshi.adapters.RuntimeJsonAdapterFactoryTest$EmptyMessage. Register" + + " com.squareup.moshi.adapters.PolymorphicJsonAdapterFactoryTest$EmptyMessage. Register" + " this subtype."); } } @Test public void nonStringLabelValue() throws IOException { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -116,7 +116,7 @@ public final class RuntimeJsonAdapterFactoryTest { @Test public void nonObjectDoesNotConsume() throws IOException { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -134,8 +134,8 @@ public final class RuntimeJsonAdapterFactoryTest { } @Test public void uniqueSubtypes() { - RuntimeJsonAdapterFactory factory = - RuntimeJsonAdapterFactory.of(Message.class, "type") + PolymorphicJsonAdapterFactory factory = + PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success"); try { factory.withSubtype(Success.class, "data"); @@ -146,8 +146,8 @@ public final class RuntimeJsonAdapterFactoryTest { } @Test public void uniqueLabels() { - RuntimeJsonAdapterFactory factory = - RuntimeJsonAdapterFactory.of(Message.class, "type") + PolymorphicJsonAdapterFactory factory = + PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "data"); try { factory.withSubtype(Error.class, "data"); @@ -159,7 +159,7 @@ public final class RuntimeJsonAdapterFactoryTest { @Test public void nullSafe() throws IOException { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(Success.class, "success") .withSubtype(Error.class, "error")) .build(); @@ -172,7 +172,7 @@ public final class RuntimeJsonAdapterFactoryTest { @Test public void disallowObjectBaseType() { try { - RuntimeJsonAdapterFactory.of(Object.class, "type"); + PolymorphicJsonAdapterFactory.of(Object.class, "type"); fail(); } catch (IllegalArgumentException expected) { assertThat(expected).hasMessage( @@ -186,7 +186,7 @@ public final class RuntimeJsonAdapterFactoryTest { */ @Test public void unportableTypes() throws IOException { Moshi moshi = new Moshi.Builder() - .add(RuntimeJsonAdapterFactory.of(Message.class, "type") + .add(PolymorphicJsonAdapterFactory.of(Message.class, "type") .withSubtype(MessageWithUnportableTypes.class, "unportable")) .build(); JsonAdapter adapter = moshi.adapter(Message.class);