Add support for default values in PolymorphicJsonAdapterFactory
Picking from #741 Resolves #739
This commit is contained in:
parent
5153295988
commit
fead71bca0
2 changed files with 88 additions and 7 deletions
|
@ -29,6 +29,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.CheckReturnValue;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A JsonAdapter factory for objects that include type information in the JSON. When decoding JSON
|
||||
|
@ -100,19 +101,31 @@ import javax.annotation.CheckReturnValue;
|
|||
* <p>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}.
|
||||
*
|
||||
* <p>If you want to specify a custom unknown fallback for decoding, you can do so via
|
||||
* {@link #withDefaultValue(Object)}. This instance should be immutable, as it is shared.
|
||||
*/
|
||||
public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Factory {
|
||||
final Class<T> baseType;
|
||||
final String labelKey;
|
||||
final List<String> labels;
|
||||
final List<Type> subtypes;
|
||||
@Nullable final T defaultValue;
|
||||
final boolean defaultValueSet;
|
||||
|
||||
PolymorphicJsonAdapterFactory(
|
||||
Class<T> baseType, String labelKey, List<String> labels, List<Type> subtypes) {
|
||||
Class<T> baseType,
|
||||
String labelKey,
|
||||
List<String> labels,
|
||||
List<Type> subtypes,
|
||||
@Nullable T defaultValue,
|
||||
boolean defaultValueSet) {
|
||||
this.baseType = baseType;
|
||||
this.labelKey = labelKey;
|
||||
this.labels = labels;
|
||||
this.subtypes = subtypes;
|
||||
this.defaultValue = defaultValue;
|
||||
this.defaultValueSet = defaultValueSet;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,7 +138,12 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||
if (baseType == null) throw new NullPointerException("baseType == null");
|
||||
if (labelKey == null) throw new NullPointerException("labelKey == null");
|
||||
return new PolymorphicJsonAdapterFactory<>(
|
||||
baseType, labelKey, Collections.<String>emptyList(), Collections.<Type>emptyList());
|
||||
baseType,
|
||||
labelKey,
|
||||
Collections.<String>emptyList(),
|
||||
Collections.<Type>emptyList(),
|
||||
null,
|
||||
false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,7 +161,25 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||
newLabels.add(label);
|
||||
List<Type> newSubtypes = new ArrayList<>(subtypes);
|
||||
newSubtypes.add(subtype);
|
||||
return new PolymorphicJsonAdapterFactory<>(baseType, labelKey, newLabels, newSubtypes);
|
||||
return new PolymorphicJsonAdapterFactory<>(baseType,
|
||||
labelKey,
|
||||
newLabels,
|
||||
newSubtypes,
|
||||
defaultValue,
|
||||
defaultValueSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new factory that with default to {@code defaultValue} upon decoding of unrecognized
|
||||
* labels. The default value should be immutable.
|
||||
*/
|
||||
public PolymorphicJsonAdapterFactory<T> withDefaultValue(@Nullable T defaultValue) {
|
||||
return new PolymorphicJsonAdapterFactory<>(baseType,
|
||||
labelKey,
|
||||
labels,
|
||||
subtypes,
|
||||
defaultValue,
|
||||
true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -157,7 +193,13 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||
jsonAdapters.add(moshi.adapter(subtypes.get(i)));
|
||||
}
|
||||
|
||||
return new PolymorphicJsonAdapter(labelKey, labels, subtypes, jsonAdapters).nullSafe();
|
||||
return new PolymorphicJsonAdapter(labelKey,
|
||||
labels,
|
||||
subtypes,
|
||||
jsonAdapters,
|
||||
defaultValue,
|
||||
defaultValueSet
|
||||
).nullSafe();
|
||||
}
|
||||
|
||||
static final class PolymorphicJsonAdapter extends JsonAdapter<Object> {
|
||||
|
@ -165,18 +207,26 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||
final List<String> labels;
|
||||
final List<Type> subtypes;
|
||||
final List<JsonAdapter<Object>> jsonAdapters;
|
||||
@Nullable final Object defaultValue;
|
||||
final boolean defaultValueSet;
|
||||
|
||||
/** Single-element options containing the label's key only. */
|
||||
final JsonReader.Options labelKeyOptions;
|
||||
/** Corresponds to subtypes. */
|
||||
final JsonReader.Options labelOptions;
|
||||
|
||||
PolymorphicJsonAdapter(String labelKey, List<String> labels,
|
||||
List<Type> subtypes, List<JsonAdapter<Object>> jsonAdapters) {
|
||||
PolymorphicJsonAdapter(String labelKey,
|
||||
List<String> labels,
|
||||
List<Type> subtypes,
|
||||
List<JsonAdapter<Object>> jsonAdapters,
|
||||
@Nullable Object defaultValue,
|
||||
boolean defaultValueSet) {
|
||||
this.labelKey = labelKey;
|
||||
this.labels = labels;
|
||||
this.subtypes = subtypes;
|
||||
this.jsonAdapters = jsonAdapters;
|
||||
this.defaultValue = defaultValue;
|
||||
this.defaultValueSet = defaultValueSet;
|
||||
|
||||
this.labelKeyOptions = JsonReader.Options.of(labelKey);
|
||||
this.labelOptions = JsonReader.Options.of(labels.toArray(new String[0]));
|
||||
|
@ -184,6 +234,10 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||
|
||||
@Override public Object fromJson(JsonReader reader) throws IOException {
|
||||
int labelIndex = labelIndex(reader.peekJson());
|
||||
if (labelIndex == -1) {
|
||||
reader.skipValue();
|
||||
return defaultValue;
|
||||
}
|
||||
return jsonAdapters.get(labelIndex).fromJson(reader);
|
||||
}
|
||||
|
||||
|
@ -197,7 +251,7 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||
}
|
||||
|
||||
int labelIndex = reader.selectString(labelOptions);
|
||||
if (labelIndex == -1) {
|
||||
if (labelIndex == -1 && !defaultValueSet) {
|
||||
throw new JsonDataException("Expected one of "
|
||||
+ labels
|
||||
+ " for key '"
|
||||
|
|
|
@ -78,6 +78,33 @@ public final class PolymorphicJsonAdapterFactoryTest {
|
|||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_OBJECT);
|
||||
}
|
||||
|
||||
@Test public void specifiedFallbackSubtype() throws IOException {
|
||||
Error fallbackError = new Error(Collections.<String, Object>emptyMap());
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(PolymorphicJsonAdapterFactory.of(Message.class, "type")
|
||||
.withSubtype(Success.class, "success")
|
||||
.withSubtype(Error.class, "error")
|
||||
.withDefaultValue(fallbackError))
|
||||
.build();
|
||||
JsonAdapter<Message> adapter = moshi.adapter(Message.class);
|
||||
|
||||
Message message = adapter.fromJson("{\"type\":\"data\",\"value\":\"Okay!\"}");
|
||||
assertThat(message).isSameAs(fallbackError);
|
||||
}
|
||||
|
||||
@Test public void specifiedNullFallbackSubtype() throws IOException {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(PolymorphicJsonAdapterFactory.of(Message.class, "type")
|
||||
.withSubtype(Success.class, "success")
|
||||
.withSubtype(Error.class, "error")
|
||||
.withDefaultValue(null))
|
||||
.build();
|
||||
JsonAdapter<Message> adapter = moshi.adapter(Message.class);
|
||||
|
||||
Message message = adapter.fromJson("{\"type\":\"data\",\"value\":\"Okay!\"}");
|
||||
assertThat(message).isNull();
|
||||
}
|
||||
|
||||
@Test public void unregisteredSubtype() {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(PolymorphicJsonAdapterFactory.of(Message.class, "type")
|
||||
|
|
Loading…
Reference in a new issue