Include labels when encoding with RuntimeJsonAdapterFactory.
Otherwise the adapter is not symmetric.
This commit is contained in:
parent
4f3c418202
commit
29bb93bc29
3 changed files with 47 additions and 24 deletions
|
@ -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<T extends Enum<T>> extends JsonAdapter<T> {
|
|||
}
|
||||
}
|
||||
|
||||
@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];
|
||||
|
||||
|
|
|
@ -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<T> implements JsonAdapter.Factory {
|
||||
|
@ -42,7 +43,7 @@ public final class RuntimeJsonAdapterFactory<T> 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 <T> RuntimeJsonAdapterFactory<T> of(Class<T> baseType, String labelKey) {
|
||||
|
@ -57,9 +58,9 @@ public final class RuntimeJsonAdapterFactory<T> 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<T> registerSubtype(Class<? extends T> subtype, String label) {
|
||||
if (subtype == null) throw new NullPointerException("subtype == null");
|
||||
|
@ -77,30 +78,32 @@ public final class RuntimeJsonAdapterFactory<T> implements JsonAdapter.Factory {
|
|||
return null;
|
||||
}
|
||||
int size = labelToType.size();
|
||||
Map<Type, JsonAdapter<Object>> typeToAdapter = new LinkedHashMap<>(size);
|
||||
Map<String, JsonAdapter<Object>> labelToAdapter = new LinkedHashMap<>(size);
|
||||
Map<Type, String> typeToLabel = new LinkedHashMap<>(size);
|
||||
for (Map.Entry<String, Type> entry : labelToType.entrySet()) {
|
||||
String label = entry.getKey();
|
||||
Type typeValue = entry.getValue();
|
||||
JsonAdapter<Object> 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<Object> objectJsonAdapter = moshi.nextAdapter(
|
||||
this, Object.class, Util.NO_ANNOTATIONS);
|
||||
return new RuntimeJsonAdapter(labelKey, labelToAdapter, typeToLabel, objectJsonAdapter)
|
||||
.nullSafe();
|
||||
}
|
||||
|
||||
static final class RuntimeJsonAdapter extends JsonAdapter<Object> {
|
||||
final String labelKey;
|
||||
final Map<String, JsonAdapter<Object>> labelToAdapter;
|
||||
final Map<Type, JsonAdapter<Object>> typeToAdapter;
|
||||
final Map<Type, String> typeToLabel;
|
||||
final JsonAdapter<Object> objectJsonAdapter;
|
||||
|
||||
RuntimeJsonAdapter(
|
||||
String labelKey,
|
||||
Map<String, JsonAdapter<Object>> labelToAdapter,
|
||||
Map<Type, JsonAdapter<Object>> typeToAdapter) {
|
||||
RuntimeJsonAdapter(String labelKey, Map<String, JsonAdapter<Object>> labelToAdapter,
|
||||
Map<Type, String> typeToLabel, JsonAdapter<Object> 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<T> implements JsonAdapter.Factory {
|
|||
|
||||
@Override public void toJson(JsonWriter writer, Object value) throws IOException {
|
||||
Class<?> type = value.getClass();
|
||||
JsonAdapter<Object> 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<Object> adapter = labelToAdapter.get(label);
|
||||
Map<String, Object> jsonValue = (Map<String, Object>) adapter.toJsonValue(value);
|
||||
|
||||
Map<String, Object> valueWithLabel = new LinkedHashMap<>(1 + jsonValue.size());
|
||||
valueWithLabel.put(labelKey, label);
|
||||
valueWithLabel.putAll(jsonValue);
|
||||
objectJsonAdapter.toJson(writer, valueWithLabel);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
|
|
|
@ -53,9 +53,9 @@ public final class RuntimeJsonAdapterFactoryTest {
|
|||
JsonAdapter<Message> 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.<String, Object>singletonMap("order", 66))))
|
||||
.isEqualTo("{\"error_logs\":{\"order\":66}}");
|
||||
.isEqualTo("{\"type\":\"error\",\"error_logs\":{\"order\":66}}");
|
||||
}
|
||||
|
||||
@Test public void unregisteredLabelValue() throws IOException {
|
||||
|
|
Loading…
Reference in a new issue