Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the adapter for Object.class to delegate. #316

Merged
merged 1 commit into from
May 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;

Expand Down Expand Up @@ -266,13 +267,45 @@ static final class EnumJsonAdapter<T extends Enum<T>> extends JsonAdapter<T> {
*/
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 {
Expand Down
110 changes: 110 additions & 0 deletions moshi/src/test/java/com/squareup/moshi/ObjectAdapterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,12 +30,17 @@
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 {
Expand Down Expand Up @@ -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;
Expand Down