mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Merge pull request #4 from square/jwilson_0810_moshi
Initial JsonAdapter structure.
This commit is contained in:
@@ -22,5 +22,10 @@
|
|||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
101
moshi/src/main/java/com/squareup/moshi/JsonAdapter.java
Normal file
101
moshi/src/main/java/com/squareup/moshi/JsonAdapter.java
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.squareup.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Java values to JSON, and JSON values to Java.
|
||||||
|
*/
|
||||||
|
public abstract class JsonAdapter<T> {
|
||||||
|
public abstract T fromJson(JsonReader reader) throws IOException;
|
||||||
|
|
||||||
|
public final T fromJson(String string) throws IOException {
|
||||||
|
return fromJson(new JsonReader(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void toJson(JsonWriter writer, T value) throws IOException;
|
||||||
|
|
||||||
|
public final String toJson(T value) throws IOException {
|
||||||
|
StringWriter stringWriter = new StringWriter();
|
||||||
|
toJson(new JsonWriter(stringWriter), value);
|
||||||
|
return stringWriter.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSON adapter equal to this JSON adapter, but with support for reading and writing
|
||||||
|
* nulls.
|
||||||
|
*/
|
||||||
|
public final JsonAdapter<T> nullSafe() {
|
||||||
|
final JsonAdapter<T> delegate = this;
|
||||||
|
return new JsonAdapter<T>() {
|
||||||
|
@Override public T fromJson(JsonReader reader) throws IOException {
|
||||||
|
if (reader.peek() == JsonToken.NULL) {
|
||||||
|
return reader.nextNull();
|
||||||
|
} else {
|
||||||
|
return delegate.fromJson(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void toJson(JsonWriter writer, T value) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
writer.nullValue();
|
||||||
|
} else {
|
||||||
|
delegate.toJson(writer, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a JSON adapter equal to this JSON adapter, but is lenient when reading and writing. */
|
||||||
|
public final JsonAdapter<T> lenient() {
|
||||||
|
final JsonAdapter<T> delegate = this;
|
||||||
|
return new JsonAdapter<T>() {
|
||||||
|
@Override public T fromJson(JsonReader reader) throws IOException {
|
||||||
|
boolean lenient = reader.isLenient();
|
||||||
|
reader.setLenient(true);
|
||||||
|
try {
|
||||||
|
return delegate.fromJson(reader);
|
||||||
|
} finally {
|
||||||
|
reader.setLenient(lenient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void toJson(JsonWriter writer, T value) throws IOException {
|
||||||
|
boolean lenient = writer.isLenient();
|
||||||
|
writer.setLenient(true);
|
||||||
|
try {
|
||||||
|
delegate.toJson(writer, value);
|
||||||
|
} finally {
|
||||||
|
writer.setLenient(lenient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Factory {
|
||||||
|
/**
|
||||||
|
* Attempts to create an adapter for {@code type} annotated with {@code annotations}. This
|
||||||
|
* returns the adapter if one was created, or null if this factory isn't capable of creating
|
||||||
|
* such an adapter.
|
||||||
|
*
|
||||||
|
* <p>Implementations may use to {@link Moshi#adapter} to compose adapters of other types, or
|
||||||
|
* {@link Moshi#nextAdapter} to delegate to the underlying adapter of the same type.
|
||||||
|
*/
|
||||||
|
JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi);
|
||||||
|
}
|
||||||
|
}
|
@@ -214,7 +214,6 @@ public class JsonReader implements Closeable {
|
|||||||
private static final int NUMBER_CHAR_EXP_SIGN = 6;
|
private static final int NUMBER_CHAR_EXP_SIGN = 6;
|
||||||
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
|
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
|
||||||
|
|
||||||
|
|
||||||
/** True to accept non-spec compliant JSON */
|
/** True to accept non-spec compliant JSON */
|
||||||
private boolean lenient = false;
|
private boolean lenient = false;
|
||||||
|
|
||||||
@@ -843,12 +842,12 @@ public class JsonReader implements Closeable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes the next token from the JSON stream and asserts that it is a
|
* Consumes the next token from the JSON stream and asserts that it is a
|
||||||
* literal null.
|
* literal null. Returns null.
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException if the next token is not null or if this
|
* @throws IllegalStateException if the next token is not null or if this
|
||||||
* reader is closed.
|
* reader is closed.
|
||||||
*/
|
*/
|
||||||
public void nextNull() throws IOException {
|
public <T> T nextNull() throws IOException {
|
||||||
int p = peeked;
|
int p = peeked;
|
||||||
if (p == PEEKED_NONE) {
|
if (p == PEEKED_NONE) {
|
||||||
p = doPeek();
|
p = doPeek();
|
||||||
@@ -856,6 +855,7 @@ public class JsonReader implements Closeable {
|
|||||||
if (p == PEEKED_NULL) {
|
if (p == PEEKED_NULL) {
|
||||||
peeked = PEEKED_NONE;
|
peeked = PEEKED_NONE;
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
|
return null;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Expected null but was " + peek()
|
throw new IllegalStateException("Expected null but was " + peek()
|
||||||
+ " at path " + getPath());
|
+ " at path " + getPath());
|
||||||
|
89
moshi/src/main/java/com/squareup/moshi/Moshi.java
Normal file
89
moshi/src/main/java/com/squareup/moshi/Moshi.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.squareup.moshi;
|
||||||
|
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coordinates binding between JSON values and Java objects.
|
||||||
|
*/
|
||||||
|
public final class Moshi {
|
||||||
|
private final List<JsonAdapter.Factory> factories;
|
||||||
|
|
||||||
|
private Moshi(Builder builder) {
|
||||||
|
List<JsonAdapter.Factory> factories = new ArrayList<JsonAdapter.Factory>();
|
||||||
|
factories.addAll(builder.factories);
|
||||||
|
factories.add(new StandardJsonAdapterFactory());
|
||||||
|
this.factories = Collections.unmodifiableList(factories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> JsonAdapter<T> adapter(Type type) {
|
||||||
|
return adapter(type, Util.NO_ANNOTATIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a JSON adapter for {@code type}, creating it if necessary. */
|
||||||
|
public <T> JsonAdapter<T> adapter(Class<T> type) {
|
||||||
|
// TODO: cache created JSON adapters.
|
||||||
|
return adapter(type, Util.NO_ANNOTATIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> JsonAdapter<T> adapter(Type type, AnnotatedElement annotations) {
|
||||||
|
// TODO: support re-entrant calls.
|
||||||
|
return createAdapter(0, type, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> JsonAdapter<T> nextAdapter(JsonAdapter.Factory skipPast, Type type,
|
||||||
|
AnnotatedElement annotations) {
|
||||||
|
return createAdapter(factories.indexOf(skipPast) + 1, type, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") // Factories are required to return only matching JsonAdapters.
|
||||||
|
private <T> JsonAdapter<T> createAdapter(
|
||||||
|
int firstIndex, Type type, AnnotatedElement annotations) {
|
||||||
|
for (int i = firstIndex, size = factories.size(); i < size; i++) {
|
||||||
|
JsonAdapter<?> result = factories.get(i).create(type, annotations, this);
|
||||||
|
if (result != null) return (JsonAdapter<T>) result;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("no JsonAdapter for " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Builder {
|
||||||
|
private final List<JsonAdapter.Factory> factories = new ArrayList<JsonAdapter.Factory>();
|
||||||
|
|
||||||
|
public <T> Builder add(final Type type, final JsonAdapter<T> jsonAdapter) {
|
||||||
|
return add(new JsonAdapter.Factory() {
|
||||||
|
@Override public JsonAdapter<?> create(
|
||||||
|
Type targetType, AnnotatedElement annotations, Moshi moshi) {
|
||||||
|
return Util.typesMatch(type, targetType) ? jsonAdapter : null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder add(JsonAdapter.Factory jsonAdapter) {
|
||||||
|
// TODO: define precedence order. Last added wins? First added wins?
|
||||||
|
factories.add(jsonAdapter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Moshi build() {
|
||||||
|
return new Moshi(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.squareup.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
final class StandardJsonAdapterFactory implements JsonAdapter.Factory {
|
||||||
|
static final JsonAdapter<Boolean> BOOLEAN_JSON_ADAPTER = new JsonAdapter<Boolean>() {
|
||||||
|
@Override public Boolean fromJson(JsonReader reader) throws IOException {
|
||||||
|
return reader.nextBoolean();
|
||||||
|
}
|
||||||
|
@Override public void toJson(JsonWriter writer, Boolean value) throws IOException {
|
||||||
|
writer.value(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final JsonAdapter<Double> DOUBLE_JSON_ADAPTER = new JsonAdapter<Double>() {
|
||||||
|
@Override public Double fromJson(JsonReader reader) throws IOException {
|
||||||
|
return reader.nextDouble();
|
||||||
|
}
|
||||||
|
@Override public void toJson(JsonWriter writer, Double value) throws IOException {
|
||||||
|
writer.value(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final JsonAdapter<Integer> INTEGER_JSON_ADAPTER = new JsonAdapter<Integer>() {
|
||||||
|
@Override public Integer fromJson(JsonReader reader) throws IOException {
|
||||||
|
return reader.nextInt();
|
||||||
|
}
|
||||||
|
@Override public void toJson(JsonWriter writer, Integer value) throws IOException {
|
||||||
|
writer.value(value.intValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final JsonAdapter<String> STRING_JSON_ADAPTER = new JsonAdapter<String>() {
|
||||||
|
@Override public String fromJson(JsonReader reader) throws IOException {
|
||||||
|
return reader.nextString();
|
||||||
|
}
|
||||||
|
@Override public void toJson(JsonWriter writer, String value) throws IOException {
|
||||||
|
writer.value(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override public JsonAdapter<?> create(
|
||||||
|
Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||||
|
// TODO: support all 8 primitive types.
|
||||||
|
if (type == boolean.class) return BOOLEAN_JSON_ADAPTER;
|
||||||
|
if (type == double.class) return DOUBLE_JSON_ADAPTER;
|
||||||
|
if (type == int.class) return INTEGER_JSON_ADAPTER;
|
||||||
|
if (type == Boolean.class) return BOOLEAN_JSON_ADAPTER.nullSafe();
|
||||||
|
if (type == Double.class) return DOUBLE_JSON_ADAPTER.nullSafe();
|
||||||
|
if (type == Integer.class) return INTEGER_JSON_ADAPTER.nullSafe();
|
||||||
|
if (type == String.class) return STRING_JSON_ADAPTER.nullSafe();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
44
moshi/src/main/java/com/squareup/moshi/Util.java
Normal file
44
moshi/src/main/java/com/squareup/moshi/Util.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.squareup.moshi;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
final class Util {
|
||||||
|
public static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0];
|
||||||
|
|
||||||
|
public static final AnnotatedElement NO_ANNOTATIONS = new AnnotatedElement() {
|
||||||
|
@Override public boolean isAnnotationPresent(Class<? extends Annotation> aClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override public <T extends Annotation> T getAnnotation(Class<T> tClass) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override public Annotation[] getAnnotations() {
|
||||||
|
return EMPTY_ANNOTATIONS_ARRAY;
|
||||||
|
}
|
||||||
|
@Override public Annotation[] getDeclaredAnnotations() {
|
||||||
|
return EMPTY_ANNOTATIONS_ARRAY;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static boolean typesMatch(Type pattern, Type candidate) {
|
||||||
|
// TODO: permit raw types (like Set.class) to match non-raw candidates (like Set<Long>).
|
||||||
|
return pattern.equals(candidate);
|
||||||
|
}
|
||||||
|
}
|
225
moshi/src/test/java/com/squareup/moshi/MoshiTest.java
Normal file
225
moshi/src/test/java/com/squareup/moshi/MoshiTest.java
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.squareup.moshi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Locale;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public final class MoshiTest {
|
||||||
|
/** No nulls for int.class. */
|
||||||
|
@Test public void intAdapter() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
|
JsonAdapter<Integer> adapter = moshi.adapter(int.class).lenient();
|
||||||
|
assertThat(adapter.fromJson("1")).isEqualTo(1);
|
||||||
|
assertThat(adapter.toJson(2)).isEqualTo("2");
|
||||||
|
|
||||||
|
try {
|
||||||
|
adapter.fromJson("null");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
assertThat(expected.getMessage()).isEqualTo("Expected an int but was NULL at path $");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
moshi.adapter(int.class).toJson(null);
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Moshi supports nulls for Integer.class. */
|
||||||
|
@Test public void integerAdapter() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
|
JsonAdapter<Integer> adapter = moshi.adapter(Integer.class).lenient();
|
||||||
|
assertThat(adapter.fromJson("1")).isEqualTo(1);
|
||||||
|
assertThat(adapter.toJson(2)).isEqualTo("2");
|
||||||
|
assertThat(adapter.fromJson("null")).isEqualTo(null);
|
||||||
|
assertThat(adapter.toJson(null)).isEqualTo("null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void stringAdapter() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
|
JsonAdapter<String> adapter = moshi.adapter(String.class).lenient();
|
||||||
|
assertThat(adapter.fromJson("\"a\"")).isEqualTo("a");
|
||||||
|
assertThat(adapter.toJson("b")).isEqualTo("\"b\"");
|
||||||
|
assertThat(adapter.fromJson("null")).isEqualTo(null);
|
||||||
|
assertThat(adapter.toJson(null)).isEqualTo("null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void customJsonAdapter() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(Pizza.class, new PizzaAdapter())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JsonAdapter<Pizza> jsonAdapter = moshi.adapter(Pizza.class);
|
||||||
|
assertThat(jsonAdapter.toJson(new Pizza(15, true)))
|
||||||
|
.isEqualTo("{\"size\":15,\"extra cheese\":true}");
|
||||||
|
assertThat(jsonAdapter.fromJson("{\"extra cheese\":true,\"size\":18}"))
|
||||||
|
.isEqualTo(new Pizza(18, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void composingJsonAdapterFactory() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new MealDealAdapterFactory())
|
||||||
|
.add(Pizza.class, new PizzaAdapter())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
JsonAdapter<MealDeal> jsonAdapter = moshi.adapter(MealDeal.class);
|
||||||
|
assertThat(jsonAdapter.toJson(new MealDeal(new Pizza(15, true), "Pepsi")))
|
||||||
|
.isEqualTo("[{\"size\":15,\"extra cheese\":true},\"Pepsi\"]");
|
||||||
|
assertThat(jsonAdapter.fromJson("[{\"extra cheese\":true,\"size\":18},\"Coke\"]"))
|
||||||
|
.isEqualTo(new MealDeal(new Pizza(18, true), "Coke"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Uppercase
|
||||||
|
static String uppercaseString;
|
||||||
|
|
||||||
|
@Test public void delegatingJsonAdapterFactory() throws Exception {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(new UppercaseAdapterFactory())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnnotatedElement annotations = MoshiTest.class.getDeclaredField("uppercaseString");
|
||||||
|
JsonAdapter<String> adapter = moshi.<String>adapter(String.class, annotations).lenient();
|
||||||
|
assertThat(adapter.toJson("a")).isEqualTo("\"A\"");
|
||||||
|
assertThat(adapter.fromJson("\"b\"")).isEqualTo("B");
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Pizza {
|
||||||
|
final int diameter;
|
||||||
|
final boolean extraCheese;
|
||||||
|
|
||||||
|
Pizza(int diameter, boolean extraCheese) {
|
||||||
|
this.diameter = diameter;
|
||||||
|
this.extraCheese = extraCheese;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
return o instanceof Pizza
|
||||||
|
&& ((Pizza) o).diameter == diameter
|
||||||
|
&& ((Pizza) o).extraCheese == extraCheese;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return diameter * (extraCheese ? 31 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MealDeal {
|
||||||
|
final Pizza pizza;
|
||||||
|
final String drink;
|
||||||
|
|
||||||
|
MealDeal(Pizza pizza, String drink) {
|
||||||
|
this.pizza = pizza;
|
||||||
|
this.drink = drink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
return o instanceof MealDeal
|
||||||
|
&& ((MealDeal) o).pizza.equals(pizza)
|
||||||
|
&& ((MealDeal) o).drink.equals(drink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return pizza.hashCode() + (31 * drink.hashCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PizzaAdapter extends JsonAdapter<Pizza> {
|
||||||
|
@Override public Pizza fromJson(JsonReader reader) throws IOException {
|
||||||
|
int diameter = 13;
|
||||||
|
boolean extraCheese = false;
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
String name = reader.nextName();
|
||||||
|
if (name.equals("size")) {
|
||||||
|
diameter = reader.nextInt();
|
||||||
|
} else if (name.equals("extra cheese")) {
|
||||||
|
extraCheese = reader.nextBoolean();
|
||||||
|
} else {
|
||||||
|
reader.skipValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
return new Pizza(diameter, extraCheese);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void toJson(JsonWriter writer, Pizza value) throws IOException {
|
||||||
|
writer.beginObject();
|
||||||
|
writer.name("size").value(value.diameter);
|
||||||
|
writer.name("extra cheese").value(value.extraCheese);
|
||||||
|
writer.endObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MealDealAdapterFactory implements JsonAdapter.Factory {
|
||||||
|
@Override public JsonAdapter<?> create(
|
||||||
|
Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||||
|
if (!type.equals(MealDeal.class)) return null;
|
||||||
|
|
||||||
|
final JsonAdapter<Pizza> pizzaAdapter = moshi.adapter(Pizza.class);
|
||||||
|
final JsonAdapter<String> drinkAdapter = moshi.adapter(String.class);
|
||||||
|
return new JsonAdapter<MealDeal>() {
|
||||||
|
@Override public MealDeal fromJson(JsonReader reader) throws IOException {
|
||||||
|
reader.beginArray();
|
||||||
|
Pizza pizza = pizzaAdapter.fromJson(reader);
|
||||||
|
String drink = drinkAdapter.fromJson(reader);
|
||||||
|
reader.endArray();
|
||||||
|
return new MealDeal(pizza, drink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void toJson(JsonWriter writer, MealDeal value) throws IOException {
|
||||||
|
writer.beginArray();
|
||||||
|
pizzaAdapter.toJson(writer, value.pizza);
|
||||||
|
drinkAdapter.toJson(writer, value.drink);
|
||||||
|
writer.endArray();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface Uppercase {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class UppercaseAdapterFactory implements JsonAdapter.Factory {
|
||||||
|
@Override public JsonAdapter<?> create(
|
||||||
|
Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||||
|
if (!type.equals(String.class)) return null;
|
||||||
|
if (!annotations.isAnnotationPresent(Uppercase.class)) return null;
|
||||||
|
|
||||||
|
final JsonAdapter<String> stringAdapter = moshi.nextAdapter(this, String.class, annotations);
|
||||||
|
return new JsonAdapter<String>() {
|
||||||
|
@Override public String fromJson(JsonReader reader) throws IOException {
|
||||||
|
String s = stringAdapter.fromJson(reader);
|
||||||
|
return s.toUpperCase(Locale.US);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void toJson(JsonWriter writer, String value) throws IOException {
|
||||||
|
stringAdapter.toJson(writer, value.toUpperCase());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
pom.xml
6
pom.xml
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
<!-- Test Dependencies -->
|
<!-- Test Dependencies -->
|
||||||
<junit.version>4.11</junit.version>
|
<junit.version>4.11</junit.version>
|
||||||
|
<assertj.version>1.6.1</assertj.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
@@ -63,6 +64,11 @@
|
|||||||
<artifactId>okio</artifactId>
|
<artifactId>okio</artifactId>
|
||||||
<version>${okio.version}</version>
|
<version>${okio.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>${assertj.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user