diff --git a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java index bdb8da1..526dac4 100644 --- a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java +++ b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java @@ -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 { private final Moshi moshi; + private final JsonAdapter listJsonAdapter; + private final JsonAdapter mapAdapter; + private final JsonAdapter stringAdapter; + private final JsonAdapter doubleAdapter; + private final JsonAdapter 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 { diff --git a/moshi/src/test/java/com/squareup/moshi/ObjectAdapterTest.java b/moshi/src/test/java/com/squareup/moshi/ObjectAdapterTest.java index d85daad..a844d94 100644 --- a/moshi/src/test/java/com/squareup/moshi/ObjectAdapterTest.java +++ b/moshi/src/test/java/com/squareup/moshi/ObjectAdapterTest.java @@ -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 stringAdapter = new JsonAdapter() { + @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 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 annotations, Moshi moshi) { + if (type != Object.class) return null; + + final JsonAdapter delegate = moshi.nextAdapter(this, Object.class, annotations); + return new JsonAdapter() { + @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 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> listAdapter = new JsonAdapter>() { + @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 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> mapAdapter = new JsonAdapter>() { + @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 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 items;