From 29bb93bc29d8fe16b24e20d52c0ef2b09dbee7df Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Sun, 9 Sep 2018 17:43:08 -0400 Subject: [PATCH] Include labels when encoding with RuntimeJsonAdapterFactory. Otherwise the adapter is not symmetric. --- .../moshi/adapters/EnumJsonAdapter.java | 18 ++++++- .../adapters/RuntimeJsonAdapterFactory.java | 49 +++++++++++-------- .../RuntimeJsonAdapterFactoryTest.java | 4 +- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java b/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java index 359ddce..8bf1637 100644 --- a/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java +++ b/adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.squareup.moshi.adapters; import com.squareup.moshi.Json; @@ -7,7 +22,6 @@ import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import java.io.IOException; import java.util.Arrays; -import javax.annotation.Nonnull; import javax.annotation.Nullable; /** @@ -55,7 +69,7 @@ public final class EnumJsonAdapter> extends JsonAdapter { } } - @Override public @Nonnull T fromJson(JsonReader reader) throws IOException { + @Override public @Nullable T fromJson(JsonReader reader) throws IOException { int index = reader.selectString(options); if (index != -1) return constants[index]; diff --git a/adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java b/adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java index 44b97d2..1e805c1 100644 --- a/adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java +++ b/adapters/src/main/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactory.java @@ -21,6 +21,7 @@ import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import com.squareup.moshi.Moshi; import com.squareup.moshi.Types; +import com.squareup.moshi.internal.Util; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @@ -31,7 +32,7 @@ import javax.annotation.CheckReturnValue; /** * A JsonAdapter factory for polymorphic types. This is useful when the type is not known before - * deserializing the JSON. This factory's adapters expect JSON in the format of a JSON object with a + * 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. */ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { @@ -42,7 +43,7 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { /** * @param baseType The base type for which this factory will create adapters. * @param labelKey The key in the JSON object whose value determines the type to which to map the - * JSON object. + * JSON object. */ @CheckReturnValue public static RuntimeJsonAdapterFactory of(Class baseType, String labelKey) { @@ -57,9 +58,9 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { } /** - * Register the subtype that can be created based on the label. When deserializing, if a label - * that was not registered is found, a JsonDataException will be thrown. When serializing, if a - * type that was not registered is used, an IllegalArgumentException will be thrown. + * Register the subtype that can be created based on the label. When an unknown type is found + * during encoding an {@linkplain IllegalArgumentException} will be thrown. When an unknown label + * is found during decoding a {@linkplain JsonDataException} will be thrown. */ public RuntimeJsonAdapterFactory registerSubtype(Class subtype, String label) { if (subtype == null) throw new NullPointerException("subtype == null"); @@ -77,30 +78,32 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { return null; } int size = labelToType.size(); - Map> typeToAdapter = new LinkedHashMap<>(size); Map> labelToAdapter = new LinkedHashMap<>(size); + Map typeToLabel = new LinkedHashMap<>(size); for (Map.Entry entry : labelToType.entrySet()) { String label = entry.getKey(); Type typeValue = entry.getValue(); - JsonAdapter adapter = moshi.adapter(typeValue); - labelToAdapter.put(label, adapter); - typeToAdapter.put(typeValue, adapter); + typeToLabel.put(typeValue, label); + labelToAdapter.put(label, moshi.adapter(typeValue)); } - return new RuntimeJsonAdapter(labelKey, labelToAdapter, typeToAdapter).nullSafe(); + JsonAdapter objectJsonAdapter = moshi.nextAdapter( + this, Object.class, Util.NO_ANNOTATIONS); + return new RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel, objectJsonAdapter) + .nullSafe(); } static final class RuntimeJsonAdapter extends JsonAdapter { final String labelKey; final Map> labelToAdapter; - final Map> typeToAdapter; + final Map typeToLabel; + final JsonAdapter objectJsonAdapter; - RuntimeJsonAdapter( - String labelKey, - Map> labelToAdapter, - Map> typeToAdapter) { + RuntimeJsonAdapter(String labelKey, Map> labelToAdapter, + Map typeToLabel, JsonAdapter objectJsonAdapter) { this.labelKey = labelKey; this.labelToAdapter = labelToAdapter; - this.typeToAdapter = typeToAdapter; + this.typeToLabel = typeToLabel; + this.objectJsonAdapter = objectJsonAdapter; } @Override public Object fromJson(JsonReader reader) throws IOException { @@ -138,17 +141,23 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { @Override public void toJson(JsonWriter writer, Object value) throws IOException { Class type = value.getClass(); - JsonAdapter adapter = typeToAdapter.get(type); - if (adapter == null) { + String label = typeToLabel.get(type); + if (label == null) { throw new IllegalArgumentException("Expected one of " - + typeToAdapter.keySet() + + typeToLabel.keySet() + " but found " + value + ", a " + value.getClass() + ". Register this subtype."); } - adapter.toJson(writer, value); + JsonAdapter adapter = labelToAdapter.get(label); + Map jsonValue = (Map) adapter.toJsonValue(value); + + Map valueWithLabel = new LinkedHashMap<>(1 + jsonValue.size()); + valueWithLabel.put(labelKey, label); + valueWithLabel.putAll(jsonValue); + objectJsonAdapter.toJson(writer, valueWithLabel); } @Override public String toString() { diff --git a/adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java b/adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java index a105fd5..6e04f53 100644 --- a/adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java +++ b/adapters/src/test/java/com/squareup/moshi/adapters/RuntimeJsonAdapterFactoryTest.java @@ -53,9 +53,9 @@ public final class RuntimeJsonAdapterFactoryTest { JsonAdapter adapter = moshi.adapter(Message.class); assertThat(adapter.toJson(new Success("Okay!"))) - .isEqualTo("{\"value\":\"Okay!\"}"); + .isEqualTo("{\"type\":\"success\",\"value\":\"Okay!\"}"); assertThat(adapter.toJson(new Error(Collections.singletonMap("order", 66)))) - .isEqualTo("{\"error_logs\":{\"order\":66}}"); + .isEqualTo("{\"type\":\"error\",\"error_logs\":{\"order\":66}}"); } @Test public void unregisteredLabelValue() throws IOException {