mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Merge pull request #778 from square/z/defaultValue
Add support for default values in PolymorphicJsonAdapterFactory
This commit is contained in:
@@ -29,6 +29,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.CheckReturnValue;
|
import javax.annotation.CheckReturnValue;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A JsonAdapter factory for objects that include type information in the JSON. When decoding JSON
|
* 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
|
* <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
|
* JsonDataException}. If an unknown type is encountered when encoding, this will throw an {@link
|
||||||
* IllegalArgumentException}.
|
* 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 {
|
public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Factory {
|
||||||
final Class<T> baseType;
|
final Class<T> baseType;
|
||||||
final String labelKey;
|
final String labelKey;
|
||||||
final List<String> labels;
|
final List<String> labels;
|
||||||
final List<Type> subtypes;
|
final List<Type> subtypes;
|
||||||
|
@Nullable final T defaultValue;
|
||||||
|
final boolean defaultValueSet;
|
||||||
|
|
||||||
PolymorphicJsonAdapterFactory(
|
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.baseType = baseType;
|
||||||
this.labelKey = labelKey;
|
this.labelKey = labelKey;
|
||||||
this.labels = labels;
|
this.labels = labels;
|
||||||
this.subtypes = subtypes;
|
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 (baseType == null) throw new NullPointerException("baseType == null");
|
||||||
if (labelKey == null) throw new NullPointerException("labelKey == null");
|
if (labelKey == null) throw new NullPointerException("labelKey == null");
|
||||||
return new PolymorphicJsonAdapterFactory<>(
|
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);
|
newLabels.add(label);
|
||||||
List<Type> newSubtypes = new ArrayList<>(subtypes);
|
List<Type> newSubtypes = new ArrayList<>(subtypes);
|
||||||
newSubtypes.add(subtype);
|
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
|
@Override
|
||||||
@@ -157,7 +193,13 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||||||
jsonAdapters.add(moshi.adapter(subtypes.get(i)));
|
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> {
|
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<String> labels;
|
||||||
final List<Type> subtypes;
|
final List<Type> subtypes;
|
||||||
final List<JsonAdapter<Object>> jsonAdapters;
|
final List<JsonAdapter<Object>> jsonAdapters;
|
||||||
|
@Nullable final Object defaultValue;
|
||||||
|
final boolean defaultValueSet;
|
||||||
|
|
||||||
/** Single-element options containing the label's key only. */
|
/** Single-element options containing the label's key only. */
|
||||||
final JsonReader.Options labelKeyOptions;
|
final JsonReader.Options labelKeyOptions;
|
||||||
/** Corresponds to subtypes. */
|
/** Corresponds to subtypes. */
|
||||||
final JsonReader.Options labelOptions;
|
final JsonReader.Options labelOptions;
|
||||||
|
|
||||||
PolymorphicJsonAdapter(String labelKey, List<String> labels,
|
PolymorphicJsonAdapter(String labelKey,
|
||||||
List<Type> subtypes, List<JsonAdapter<Object>> jsonAdapters) {
|
List<String> labels,
|
||||||
|
List<Type> subtypes,
|
||||||
|
List<JsonAdapter<Object>> jsonAdapters,
|
||||||
|
@Nullable Object defaultValue,
|
||||||
|
boolean defaultValueSet) {
|
||||||
this.labelKey = labelKey;
|
this.labelKey = labelKey;
|
||||||
this.labels = labels;
|
this.labels = labels;
|
||||||
this.subtypes = subtypes;
|
this.subtypes = subtypes;
|
||||||
this.jsonAdapters = jsonAdapters;
|
this.jsonAdapters = jsonAdapters;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
this.defaultValueSet = defaultValueSet;
|
||||||
|
|
||||||
this.labelKeyOptions = JsonReader.Options.of(labelKey);
|
this.labelKeyOptions = JsonReader.Options.of(labelKey);
|
||||||
this.labelOptions = JsonReader.Options.of(labels.toArray(new String[0]));
|
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 {
|
@Override public Object fromJson(JsonReader reader) throws IOException {
|
||||||
int labelIndex = labelIndex(reader.peekJson());
|
int labelIndex = labelIndex(reader.peekJson());
|
||||||
|
if (labelIndex == -1) {
|
||||||
|
reader.skipValue();
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
return jsonAdapters.get(labelIndex).fromJson(reader);
|
return jsonAdapters.get(labelIndex).fromJson(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +251,7 @@ public final class PolymorphicJsonAdapterFactory<T> implements JsonAdapter.Facto
|
|||||||
}
|
}
|
||||||
|
|
||||||
int labelIndex = reader.selectString(labelOptions);
|
int labelIndex = reader.selectString(labelOptions);
|
||||||
if (labelIndex == -1) {
|
if (labelIndex == -1 && !defaultValueSet) {
|
||||||
throw new JsonDataException("Expected one of "
|
throw new JsonDataException("Expected one of "
|
||||||
+ labels
|
+ labels
|
||||||
+ " for key '"
|
+ " for key '"
|
||||||
|
@@ -78,6 +78,33 @@ public final class PolymorphicJsonAdapterFactoryTest {
|
|||||||
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_OBJECT);
|
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() {
|
@Test public void unregisteredSubtype() {
|
||||||
Moshi moshi = new Moshi.Builder()
|
Moshi moshi = new Moshi.Builder()
|
||||||
.add(PolymorphicJsonAdapterFactory.of(Message.class, "type")
|
.add(PolymorphicJsonAdapterFactory.of(Message.class, "type")
|
||||||
|
Reference in New Issue
Block a user