mirror of
https://github.com/fankes/moshi.git
synced 2025-10-20 00:19:21 +08:00
Change the adapter for Object.class to delegate.
Previously if we ever had an opaque Object, the content of this object would always only use the built-in adapters for its members. This changes the built-in Object adapter to do one layer of type checking and then to delegate to user-supplied adapters. The big upside of this is that application code can now change the default numeric type to use when decoding an untyped object. Typically this will be used to replace our default of Double with a user-specified numeric type like BigDecimal.
This commit is contained in:
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -266,13 +267,45 @@ final class StandardJsonAdapters {
|
||||
*/
|
||||
static final class ObjectJsonAdapter extends JsonAdapter<Object> {
|
||||
private final Moshi moshi;
|
||||
private final JsonAdapter<List> listJsonAdapter;
|
||||
private final JsonAdapter<Map> mapAdapter;
|
||||
private final JsonAdapter<String> stringAdapter;
|
||||
private final JsonAdapter<Double> doubleAdapter;
|
||||
private final JsonAdapter<Boolean> booleanAdapter;
|
||||
|
||||
ObjectJsonAdapter(Moshi moshi) {
|
||||
this.moshi = moshi;
|
||||
this.listJsonAdapter = moshi.adapter(List.class);
|
||||
this.mapAdapter = moshi.adapter(Map.class);
|
||||
this.stringAdapter = moshi.adapter(String.class);
|
||||
this.doubleAdapter = moshi.adapter(Double.class);
|
||||
this.booleanAdapter = moshi.adapter(Boolean.class);
|
||||
}
|
||||
|
||||
@Override public Object fromJson(JsonReader reader) throws IOException {
|
||||
return reader.readJsonValue();
|
||||
switch (reader.peek()) {
|
||||
case BEGIN_ARRAY:
|
||||
return listJsonAdapter.fromJson(reader);
|
||||
|
||||
case BEGIN_OBJECT:
|
||||
return mapAdapter.fromJson(reader);
|
||||
|
||||
case STRING:
|
||||
return stringAdapter.fromJson(reader);
|
||||
|
||||
case NUMBER:
|
||||
return doubleAdapter.fromJson(reader);
|
||||
|
||||
case BOOLEAN:
|
||||
return booleanAdapter.fromJson(reader);
|
||||
|
||||
case NULL:
|
||||
return reader.nextNull();
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Expected a value but was " + reader.peek() + " at path " + reader.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, Object value) throws IOException {
|
||||
|
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractList;
|
||||
import java.util.AbstractMap;
|
||||
@@ -26,12 +30,17 @@ import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
public final class ObjectAdapterTest {
|
||||
@Test public void toJsonUsesRuntimeType() throws Exception {
|
||||
@@ -170,6 +179,107 @@ public final class ObjectAdapterTest {
|
||||
assertThat(adapter.toJson(Arrays.asList(new Date(1), new Date(2)))).isEqualTo("[1,2]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the built-in adapter for Object delegates to user-supplied adapters for JSON value
|
||||
* types like strings.
|
||||
*/
|
||||
@Test public void objectAdapterDelegatesStringNamesAndValues() throws Exception {
|
||||
JsonAdapter<String> stringAdapter = new JsonAdapter<String>() {
|
||||
@Nullable @Override public String fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextString().toUpperCase(Locale.US);
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, @Nullable String value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(String.class, stringAdapter)
|
||||
.build();
|
||||
JsonAdapter<Object> objectAdapter = moshi.adapter(Object.class);
|
||||
Map<?, ?> value = (Map<?, ?>) objectAdapter.fromJson("{\"a\":\"b\", \"c\":\"d\"}");
|
||||
assertThat(value).containsExactly(entry("A", "B"), entry("C", "D"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that the built-in adapter for Object delegates to any user-supplied adapters for
|
||||
* Object. This is necessary to customize adapters for primitives like numbers.
|
||||
*/
|
||||
@Test public void objectAdapterDelegatesObjects() throws Exception {
|
||||
JsonAdapter.Factory objectFactory = new JsonAdapter.Factory() {
|
||||
@Override public @Nullable JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (type != Object.class) return null;
|
||||
|
||||
final JsonAdapter<Object> delegate = moshi.nextAdapter(this, Object.class, annotations);
|
||||
return new JsonAdapter<Object>() {
|
||||
@Override public @Nullable Object fromJson(JsonReader reader) throws IOException {
|
||||
if (reader.peek() != JsonReader.Token.NUMBER) {
|
||||
return delegate.fromJson(reader);
|
||||
} else {
|
||||
return new BigDecimal(reader.nextString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, @Nullable Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(objectFactory)
|
||||
.build();
|
||||
JsonAdapter<Object> objectAdapter = moshi.adapter(Object.class);
|
||||
List<?> value = (List<?>) objectAdapter.fromJson("[0, 1, 2.0, 3.14]");
|
||||
assertThat(value).isEqualTo(Arrays.asList(new BigDecimal("0"), new BigDecimal("1"),
|
||||
new BigDecimal("2.0"), new BigDecimal("3.14")));
|
||||
}
|
||||
|
||||
/** Confirm that the built-in adapter for Object delegates to user-supplied adapters for lists. */
|
||||
@Test public void objectAdapterDelegatesLists() throws Exception {
|
||||
JsonAdapter<List<?>> listAdapter = new JsonAdapter<List<?>>() {
|
||||
@Override public @Nullable List<?> fromJson(JsonReader reader) throws IOException {
|
||||
reader.skipValue();
|
||||
return singletonList("z");
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, @Nullable List<?> value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(List.class, listAdapter)
|
||||
.build();
|
||||
JsonAdapter<Object> objectAdapter = moshi.adapter(Object.class);
|
||||
Map<?, ?> mapOfList = (Map<?, ?>) objectAdapter.fromJson("{\"a\":[\"b\"]}");
|
||||
assertThat(mapOfList).isEqualTo(singletonMap("a", singletonList("z")));
|
||||
}
|
||||
|
||||
/** Confirm that the built-in adapter for Object delegates to user-supplied adapters for maps. */
|
||||
@Test public void objectAdapterDelegatesMaps() throws Exception {
|
||||
JsonAdapter<Map<?, ?>> mapAdapter = new JsonAdapter<Map<?, ?>>() {
|
||||
@Override public @Nullable Map<?, ?> fromJson(JsonReader reader) throws IOException {
|
||||
reader.skipValue();
|
||||
return singletonMap("x", "y");
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, @Nullable Map<?, ?> value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(Map.class, mapAdapter)
|
||||
.build();
|
||||
JsonAdapter<Object> objectAdapter = moshi.adapter(Object.class);
|
||||
List<?> listOfMap = (List<?>) objectAdapter.fromJson("[{\"b\":\"c\"}]");
|
||||
assertThat(listOfMap).isEqualTo(singletonList(singletonMap("x", "y")));
|
||||
}
|
||||
|
||||
static class Delivery {
|
||||
String address;
|
||||
List<Object> items;
|
||||
|
Reference in New Issue
Block a user