mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
New @JsonQualifier annotation.
Works like JSR-330's @Qualifier annotation. You may have multiple qualifiers, or none.
This commit is contained in:
@@ -16,29 +16,30 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
// TODO: support qualifier annotations.
|
||||
// TODO: support @Nullable
|
||||
// TODO: path in JsonWriter.
|
||||
|
||||
final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
private final Map<Type, ToAdapter> toAdapters;
|
||||
private final Map<Type, FromAdapter> fromAdapters;
|
||||
private final List<AdapterMethod> toAdapters;
|
||||
private final List<AdapterMethod> fromAdapters;
|
||||
|
||||
AdapterMethodsFactory(Map<Type, ToAdapter> toAdapters, Map<Type, FromAdapter> fromAdapters) {
|
||||
public AdapterMethodsFactory(List<AdapterMethod> toAdapters, List<AdapterMethod> fromAdapters) {
|
||||
this.toAdapters = toAdapters;
|
||||
this.fromAdapters = fromAdapters;
|
||||
}
|
||||
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, final Moshi moshi) {
|
||||
final ToAdapter toAdapter = toAdapters.get(type);
|
||||
final FromAdapter fromAdapter = fromAdapters.get(type);
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, final Moshi moshi) {
|
||||
final AdapterMethod toAdapter = get(toAdapters, type, annotations);
|
||||
final AdapterMethod fromAdapter = get(fromAdapters, type, annotations);
|
||||
if (toAdapter == null && fromAdapter == null) return null;
|
||||
|
||||
final JsonAdapter<Object> delegate = toAdapter == null || fromAdapter == null
|
||||
@@ -56,7 +57,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
|
||||
throw new JsonDataException(e.getCause().getMessage()); // TODO: more context?
|
||||
throw new JsonDataException(e.getCause()); // TODO: more context?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +72,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
|
||||
throw new JsonDataException(e.getCause().getMessage()); // TODO: more context?
|
||||
throw new JsonDataException(e.getCause()); // TODO: more context?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,29 +80,31 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
}
|
||||
|
||||
public static AdapterMethodsFactory get(Object adapter) {
|
||||
Map<Type, ToAdapter> toAdapters = new LinkedHashMap<>();
|
||||
Map<Type, FromAdapter> fromAdapters = new LinkedHashMap<>();
|
||||
List<AdapterMethod> toAdapters = new ArrayList<>();
|
||||
List<AdapterMethod> fromAdapters = new ArrayList<>();
|
||||
|
||||
for (Class<?> c = adapter.getClass(); c != Object.class; c = c.getSuperclass()) {
|
||||
for (Method m : c.getDeclaredMethods()) {
|
||||
if (m.isAnnotationPresent(ToJson.class)) {
|
||||
ToAdapter toAdapter = toAdapter(adapter, m);
|
||||
ToAdapter replaced = toAdapters.put(toAdapter.type, toAdapter);
|
||||
if (replaced != null) {
|
||||
AdapterMethod toAdapter = toAdapter(adapter, m);
|
||||
AdapterMethod conflicting = get(toAdapters, toAdapter.type, toAdapter.annotations);
|
||||
if (conflicting != null) {
|
||||
throw new IllegalArgumentException("Conflicting @ToJson methods:\n"
|
||||
+ " " + replaced.method + "\n"
|
||||
+ " " + conflicting.method + "\n"
|
||||
+ " " + toAdapter.method);
|
||||
}
|
||||
toAdapters.add(toAdapter);
|
||||
}
|
||||
|
||||
if (m.isAnnotationPresent(FromJson.class)) {
|
||||
FromAdapter fromAdapter = fromAdapter(adapter, m);
|
||||
FromAdapter replaced = fromAdapters.put(fromAdapter.type, fromAdapter);
|
||||
if (replaced != null) {
|
||||
AdapterMethod fromAdapter = fromAdapter(adapter, m);
|
||||
AdapterMethod conflicting = get(fromAdapters, fromAdapter.type, fromAdapter.annotations);
|
||||
if (conflicting != null) {
|
||||
throw new IllegalArgumentException("Conflicting @FromJson methods:\n"
|
||||
+ " " + replaced.method + "\n"
|
||||
+ " " + conflicting.method + "\n"
|
||||
+ " " + fromAdapter.method);
|
||||
}
|
||||
fromAdapters.add(fromAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +121,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
* Returns an object that calls a {@code method} method on {@code adapter} in service of
|
||||
* converting an object to JSON.
|
||||
*/
|
||||
static ToAdapter toAdapter(Object adapter, Method method) {
|
||||
static AdapterMethod toAdapter(Object adapter, Method method) {
|
||||
method.setAccessible(true);
|
||||
Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
final Type returnType = method.getGenericReturnType();
|
||||
@@ -126,7 +129,10 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
if (parameterTypes.length == 2
|
||||
&& parameterTypes[0] == JsonWriter.class
|
||||
&& returnType == void.class) {
|
||||
return new ToAdapter(parameterTypes[1], adapter, method) {
|
||||
// public void pointToJson(JsonWriter jsonWriter, Point point) throws Exception {
|
||||
Set<? extends Annotation> parameterAnnotations
|
||||
= Util.jsonAnnotations(method.getParameterAnnotations()[1]);
|
||||
return new AdapterMethod(parameterTypes[1], parameterAnnotations, adapter, method) {
|
||||
@Override public void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, InvocationTargetException, IllegalAccessException {
|
||||
method.invoke(adapter, writer, value);
|
||||
@@ -134,10 +140,14 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
};
|
||||
|
||||
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
||||
return new ToAdapter(parameterTypes[0], adapter, method) {
|
||||
// public List<Integer> pointToJson(Point point) throws Exception {
|
||||
final Set<? extends Annotation> returnTypeAnnotations = Util.jsonAnnotations(method);
|
||||
Set<? extends Annotation> parameterAnnotations =
|
||||
Util.jsonAnnotations(method.getParameterAnnotations()[0]);
|
||||
return new AdapterMethod(parameterTypes[0], parameterAnnotations, adapter, method) {
|
||||
@Override public void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, InvocationTargetException, IllegalAccessException {
|
||||
JsonAdapter<Object> delegate = moshi.adapter(returnType, method);
|
||||
JsonAdapter<Object> delegate = moshi.adapter(returnType, returnTypeAnnotations);
|
||||
Object intermediate = method.invoke(adapter, value);
|
||||
delegate.toJson(writer, intermediate);
|
||||
}
|
||||
@@ -151,26 +161,11 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class ToAdapter {
|
||||
final Type type;
|
||||
final Object adapter;
|
||||
final Method method;
|
||||
|
||||
public ToAdapter(Type type, Object adapter, Method method) {
|
||||
this.type = type;
|
||||
this.adapter = adapter;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public abstract void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that calls a {@code method} method on {@code adapter} in service of
|
||||
* converting an object from JSON.
|
||||
*/
|
||||
static FromAdapter fromAdapter(Object adapter, Method method) {
|
||||
static AdapterMethod fromAdapter(Object adapter, Method method) {
|
||||
method.setAccessible(true);
|
||||
final Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
final Type returnType = method.getGenericReturnType();
|
||||
@@ -179,7 +174,8 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
&& parameterTypes[0] == JsonReader.class
|
||||
&& returnType != void.class) {
|
||||
// public Point pointFromJson(JsonReader jsonReader) throws Exception {
|
||||
return new FromAdapter(returnType, adapter, method) {
|
||||
Set<? extends Annotation> returnTypeAnnotations = Util.jsonAnnotations(method);
|
||||
return new AdapterMethod(returnType, returnTypeAnnotations, adapter, method) {
|
||||
@Override public Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
return method.invoke(adapter, reader);
|
||||
@@ -188,10 +184,13 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
|
||||
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
||||
// public Point pointFromJson(List<Integer> o) throws Exception {
|
||||
return new FromAdapter(returnType, adapter, method) {
|
||||
Set<? extends Annotation> returnTypeAnnotations = Util.jsonAnnotations(method);
|
||||
final Set<? extends Annotation> parameterAnnotations
|
||||
= Util.jsonAnnotations(method.getParameterAnnotations()[0]);
|
||||
return new AdapterMethod(returnType, returnTypeAnnotations, adapter, method) {
|
||||
@Override public Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
JsonAdapter<Object> delegate = moshi.adapter(parameterTypes[0]);
|
||||
JsonAdapter<Object> delegate = moshi.adapter(parameterTypes[0], parameterAnnotations);
|
||||
Object intermediate = delegate.fromJson(reader);
|
||||
return method.invoke(adapter, intermediate);
|
||||
}
|
||||
@@ -205,18 +204,40 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class FromAdapter {
|
||||
/** Returns the matching adapter method from the list. */
|
||||
private static AdapterMethod get(
|
||||
List<AdapterMethod> adapterMethods, Type type, Set<? extends Annotation> annotations) {
|
||||
for (int i = 0, size = adapterMethods.size(); i < size; i++) {
|
||||
AdapterMethod adapterMethod = adapterMethods.get(i);
|
||||
if (adapterMethod.type.equals(type) && adapterMethod.annotations.equals(annotations)) {
|
||||
return adapterMethod;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static abstract class AdapterMethod {
|
||||
final Type type;
|
||||
final Set<? extends Annotation> annotations;
|
||||
final Object adapter;
|
||||
final Method method;
|
||||
|
||||
public FromAdapter(Type type, Object adapter, Method method) {
|
||||
public AdapterMethod(Type type,
|
||||
Set<? extends Annotation> annotations, Object adapter, Method method) {
|
||||
this.type = type;
|
||||
this.annotations = annotations;
|
||||
this.adapter = adapter;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public abstract Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException;
|
||||
public void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,11 +16,12 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Converts arrays to JSON arrays containing their converted contents. This
|
||||
@@ -28,9 +29,11 @@ import java.util.List;
|
||||
*/
|
||||
final class ArrayJsonAdapter extends JsonAdapter<Object> {
|
||||
public static final Factory FACTORY = new Factory() {
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
Type elementType = Types.arrayComponentType(type);
|
||||
if (elementType == null) return null;
|
||||
if (!annotations.isEmpty()) return null;
|
||||
Class<?> elementClass = Types.getRawType(elementType);
|
||||
JsonAdapter<Object> elementAdapter = moshi.adapter(elementType);
|
||||
return new ArrayJsonAdapter(elementClass, elementAdapter).nullSafe();
|
||||
|
@@ -16,12 +16,13 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
@@ -31,9 +32,11 @@ import java.util.TreeMap;
|
||||
*/
|
||||
final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
Class<?> rawType = Types.getRawType(type);
|
||||
if (rawType.isInterface() || rawType.isEnum() || isPlatformType(rawType)) return null;
|
||||
if (!annotations.isEmpty()) return null;
|
||||
|
||||
if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) {
|
||||
if (rawType.getSimpleName().isEmpty()) {
|
||||
@@ -66,7 +69,8 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||
|
||||
// Look up a type adapter for this type.
|
||||
Type fieldType = Types.resolve(type, rawType, field.getGenericType());
|
||||
JsonAdapter<Object> adapter = moshi.adapter(fieldType, field);
|
||||
Set<? extends Annotation> annotations = Util.jsonAnnotations(field);
|
||||
JsonAdapter<Object> adapter = moshi.adapter(fieldType, annotations);
|
||||
|
||||
// Create the binding between field and JSON.
|
||||
field.setAccessible(true);
|
||||
|
@@ -16,7 +16,7 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -27,8 +27,10 @@ import java.util.Set;
|
||||
/** Converts collection types to JSON arrays containing their converted contents. */
|
||||
abstract class CollectionJsonAdapter<C extends Collection<T>, T> extends JsonAdapter<C> {
|
||||
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
Class<?> rawType = Types.getRawType(type);
|
||||
if (!annotations.isEmpty()) return null;
|
||||
if (rawType == List.class || rawType == Collection.class) {
|
||||
return newArrayListAdapter(type, moshi).nullSafe();
|
||||
} else if (rawType == Set.class) {
|
||||
|
@@ -16,8 +16,9 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Set;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSink;
|
||||
import okio.BufferedSource;
|
||||
@@ -107,6 +108,6 @@ public abstract class JsonAdapter<T> {
|
||||
* <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);
|
||||
JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi);
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,9 @@ package com.squareup.moshi;
|
||||
|
||||
/** Thrown when a JSON document doesn't match the expected format. */
|
||||
public final class JsonDataException extends RuntimeException {
|
||||
public JsonDataException() {
|
||||
}
|
||||
|
||||
public JsonDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
30
moshi/src/main/java/com/squareup/moshi/JsonQualifier.java
Normal file
30
moshi/src/main/java/com/squareup/moshi/JsonQualifier.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/** Annotates another annotation, causing it to specialize how values are encoded and decoded. */
|
||||
@Target(ANNOTATION_TYPE)
|
||||
@Retention(RUNTIME)
|
||||
@Documented
|
||||
public @interface JsonQualifier {
|
||||
}
|
@@ -16,9 +16,10 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Converts maps with string keys to JSON objects.
|
||||
@@ -27,7 +28,9 @@ import java.util.Map;
|
||||
*/
|
||||
final class MapJsonAdapter<K, V> extends JsonAdapter<Map<K, V>> {
|
||||
public static final Factory FACTORY = new Factory() {
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (!annotations.isEmpty()) return null;
|
||||
Class<?> rawType = Types.getRawType(type);
|
||||
if (rawType != Map.class) return null;
|
||||
Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType);
|
||||
|
@@ -17,11 +17,11 @@ package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Coordinates binding between JSON values and Java objects.
|
||||
@@ -51,19 +51,22 @@ public final class Moshi {
|
||||
return adapter(type, Util.NO_ANNOTATIONS);
|
||||
}
|
||||
|
||||
public <T> JsonAdapter<T> adapter(Type type, AnnotatedElement annotations) {
|
||||
// TODO: support re-entrant calls.
|
||||
public <T> JsonAdapter<T> adapter(Type type, Set<? extends Annotation> annotations) {
|
||||
return createAdapter(0, type, annotations);
|
||||
}
|
||||
|
||||
public <T> JsonAdapter<T> nextAdapter(JsonAdapter.Factory skipPast, Type type,
|
||||
AnnotatedElement annotations) {
|
||||
Set<? extends Annotation> annotations) {
|
||||
return createAdapter(factories.indexOf(skipPast) + 1, type, annotations);
|
||||
}
|
||||
|
||||
public <T> JsonAdapter<T> nextAdapter(JsonAdapter.Factory skipPast, Type type) {
|
||||
return nextAdapter(skipPast, type, Util.NO_ANNOTATIONS);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked") // Factories are required to return only matching JsonAdapters.
|
||||
private <T> JsonAdapter<T> createAdapter(
|
||||
int firstIndex, Type type, AnnotatedElement annotations) {
|
||||
int firstIndex, Type type, Set<? extends Annotation> annotations) {
|
||||
List<DeferredAdapter<?>> deferredAdapters = reentrantCalls.get();
|
||||
if (deferredAdapters == null) {
|
||||
deferredAdapters = new ArrayList<>();
|
||||
@@ -91,7 +94,7 @@ public final class Moshi {
|
||||
deferredAdapters.remove(deferredAdapters.size() - 1);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("no JsonAdapter for " + type);
|
||||
throw new IllegalArgumentException("no JsonAdapter for " + type + " annotated " + annotations);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
@@ -103,8 +106,8 @@ public final class Moshi {
|
||||
|
||||
return add(new JsonAdapter.Factory() {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type targetType, AnnotatedElement annotations, Moshi moshi) {
|
||||
return Util.typesMatch(type, targetType) ? jsonAdapter : null;
|
||||
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
return annotations.isEmpty() && Util.typesMatch(type, targetType) ? jsonAdapter : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -114,13 +117,16 @@ public final class Moshi {
|
||||
if (type == null) throw new IllegalArgumentException("type == null");
|
||||
if (annotation == null) throw new IllegalArgumentException("annotation == null");
|
||||
if (jsonAdapter == null) throw new IllegalArgumentException("jsonAdapter == null");
|
||||
if (!annotation.isAnnotationPresent(JsonQualifier.class)) {
|
||||
throw new IllegalArgumentException(annotation + " does not have @JsonQualifier");
|
||||
}
|
||||
|
||||
return add(new JsonAdapter.Factory() {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type targetType, AnnotatedElement annotations, Moshi moshi) {
|
||||
return Util.typesMatch(type, targetType) && annotations.isAnnotationPresent(annotation)
|
||||
? jsonAdapter
|
||||
: null;
|
||||
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (!Util.typesMatch(type, targetType)) return null;
|
||||
if (!Util.isAnnotationPresent(annotations, annotation)) return null;
|
||||
return jsonAdapter;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -150,10 +156,10 @@ public final class Moshi {
|
||||
*/
|
||||
private static class DeferredAdapter<T> extends JsonAdapter<T> {
|
||||
private Type type;
|
||||
private AnnotatedElement annotations;
|
||||
private Set<? extends Annotation> annotations;
|
||||
private JsonAdapter<T> delegate;
|
||||
|
||||
public DeferredAdapter(Type type, AnnotatedElement annotations) {
|
||||
public DeferredAdapter(Type type, Set<? extends Annotation> annotations) {
|
||||
this.type = type;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
@@ -16,13 +16,16 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
final class StandardJsonAdapters {
|
||||
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (!annotations.isEmpty()) return null;
|
||||
if (type == boolean.class) return BOOLEAN_JSON_ADAPTER;
|
||||
if (type == byte.class) return BYTE_JSON_ADAPTER;
|
||||
if (type == char.class) return CHARACTER_JSON_ADAPTER;
|
||||
|
@@ -18,27 +18,39 @@ package com.squareup.moshi;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
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 final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static Set<? extends Annotation> jsonAnnotations(AnnotatedElement annotatedElement) {
|
||||
return jsonAnnotations(annotatedElement.getAnnotations());
|
||||
}
|
||||
|
||||
public static Set<? extends Annotation> jsonAnnotations(Annotation[] annotations) {
|
||||
Set<Annotation> result = null;
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation.annotationType().isAnnotationPresent(JsonQualifier.class)) {
|
||||
if (result == null) result = new LinkedHashSet<>();
|
||||
result.add(annotation);
|
||||
}
|
||||
}
|
||||
return result != null ? Collections.unmodifiableSet(result) : Util.NO_ANNOTATIONS;
|
||||
}
|
||||
|
||||
public static boolean isAnnotationPresent(
|
||||
Set<? extends Annotation> annotations, Class<? extends Annotation> annotationClass) {
|
||||
if (annotations.isEmpty()) return false; // Save an iterator in the common case.
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation.annotationType() == annotationClass) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -16,9 +16,10 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Set;
|
||||
import org.junit.Test;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
@@ -63,10 +64,12 @@ public final class CircularAdaptersTest {
|
||||
}
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@JsonQualifier
|
||||
public @interface Left {
|
||||
}
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@JsonQualifier
|
||||
public @interface Right {
|
||||
}
|
||||
|
||||
@@ -97,19 +100,20 @@ public final class CircularAdaptersTest {
|
||||
* work.
|
||||
*/
|
||||
static class PrefixingNodeFactory implements JsonAdapter.Factory {
|
||||
@Override public JsonAdapter<?> create(Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (type != Node.class) return null;
|
||||
|
||||
final String prefix;
|
||||
if (annotations.isAnnotationPresent(Left.class)) {
|
||||
if (Util.isAnnotationPresent(annotations, Left.class)) {
|
||||
prefix = "L ";
|
||||
} else if (annotations.isAnnotationPresent(Right.class)) {
|
||||
} else if (Util.isAnnotationPresent(annotations, Right.class)) {
|
||||
prefix = "R ";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
final JsonAdapter<Node> delegate = moshi.nextAdapter(this, Node.class, annotations);
|
||||
final JsonAdapter<Node> delegate = moshi.nextAdapter(this, Node.class);
|
||||
|
||||
return new JsonAdapter<Node>() {
|
||||
@Override public void toJson(JsonWriter writer, Node value) throws IOException {
|
||||
|
@@ -391,7 +391,7 @@ public final class ClassJsonAdapterTest {
|
||||
|
||||
private <T> String toJson(Class<T> type, T value) throws IOException {
|
||||
@SuppressWarnings("unchecked") // Factory.create returns an adapter that matches its argument.
|
||||
JsonAdapter<T> jsonAdapter = (JsonAdapter<T>) ClassJsonAdapter.FACTORY.create(
|
||||
JsonAdapter<T> jsonAdapter = (JsonAdapter<T>) ClassJsonAdapter.FACTORY.create(
|
||||
type, NO_ANNOTATIONS, moshi);
|
||||
|
||||
// Wrap in an array to avoid top-level object warnings without going completely lenient.
|
||||
@@ -409,7 +409,7 @@ public final class ClassJsonAdapterTest {
|
||||
|
||||
private <T> T fromJson(Class<T> type, String json) throws IOException {
|
||||
@SuppressWarnings("unchecked") // Factory.create returns an adapter that matches its argument.
|
||||
JsonAdapter<T> jsonAdapter = (JsonAdapter<T>) ClassJsonAdapter.FACTORY.create(
|
||||
JsonAdapter<T> jsonAdapter = (JsonAdapter<T>) ClassJsonAdapter.FACTORY.create(
|
||||
type, NO_ANNOTATIONS, moshi);
|
||||
// Wrap in an array to avoid top-level object warnings without going completely lenient.
|
||||
JsonReader jsonReader = newReader("[" + json + "]");
|
||||
|
367
moshi/src/test/java/com/squareup/moshi/JsonQualifiersTest.java
Normal file
367
moshi/src/test/java/com/squareup/moshi/JsonQualifiersTest.java
Normal file
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.util.Date;
|
||||
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 JsonQualifiersTest {
|
||||
@Test public void builtInTypes() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new BuiltInTypesJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<StringAndFooString> adapter = moshi.adapter(StringAndFooString.class);
|
||||
|
||||
StringAndFooString v1 = new StringAndFooString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobar\"}");
|
||||
|
||||
StringAndFooString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobar\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
static class BuiltInTypesJsonAdapter {
|
||||
@ToJson String fooPrefixStringToString(@FooPrefix String s) {
|
||||
return "foo" + s;
|
||||
}
|
||||
|
||||
@FromJson @FooPrefix String fooPrefixStringFromString(String s) throws Exception {
|
||||
if (!s.startsWith("foo")) throw new JsonDataException();
|
||||
return s.substring(3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void readerWriterJsonAdapter() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new ReaderWriterJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<StringAndFooString> adapter = moshi.adapter(StringAndFooString.class);
|
||||
|
||||
StringAndFooString v1 = new StringAndFooString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobar\"}");
|
||||
|
||||
StringAndFooString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobar\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
static class ReaderWriterJsonAdapter {
|
||||
@ToJson void fooPrefixStringToString(JsonWriter jsonWriter, @FooPrefix String s)
|
||||
throws IOException {
|
||||
jsonWriter.value("foo" + s);
|
||||
}
|
||||
|
||||
@FromJson @FooPrefix String fooPrefixStringFromString(JsonReader reader) throws Exception {
|
||||
String s = reader.nextString();
|
||||
if (!s.startsWith("foo")) throw new JsonDataException();
|
||||
return s.substring(3);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fields with this annotation get "foo" as a prefix in the JSON. */
|
||||
@Retention(RUNTIME)
|
||||
@JsonQualifier
|
||||
public @interface FooPrefix {
|
||||
}
|
||||
|
||||
/** Fields with this annotation get "baz" as a suffix in the JSON. */
|
||||
@Retention(RUNTIME)
|
||||
@JsonQualifier
|
||||
public @interface BazSuffix {
|
||||
}
|
||||
|
||||
static class StringAndFooString {
|
||||
String a;
|
||||
@FooPrefix String b;
|
||||
}
|
||||
|
||||
static class StringAndFooBazString {
|
||||
String a;
|
||||
@FooPrefix @BazSuffix String b;
|
||||
}
|
||||
|
||||
@Test public void builtInTypesWithMultipleAnnotations() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new BuiltInTypesWithMultipleAnnotationsJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<StringAndFooBazString> adapter = moshi.adapter(StringAndFooBazString.class);
|
||||
|
||||
StringAndFooBazString v1 = new StringAndFooBazString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
|
||||
StringAndFooBazString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
static class BuiltInTypesWithMultipleAnnotationsJsonAdapter {
|
||||
@ToJson String fooPrefixAndBazSuffixStringToString(@FooPrefix @BazSuffix String s) {
|
||||
return "foo" + s + "baz";
|
||||
}
|
||||
|
||||
@FromJson @FooPrefix @BazSuffix String fooPrefixAndBazSuffixStringFromString(
|
||||
String s) throws Exception {
|
||||
if (!s.startsWith("foo")) throw new JsonDataException();
|
||||
if (!s.endsWith("baz")) throw new JsonDataException();
|
||||
return s.substring(3, s.length() - 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void readerWriterWithMultipleAnnotations() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new ReaderWriterWithMultipleAnnotationsJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<StringAndFooBazString> adapter = moshi.adapter(StringAndFooBazString.class);
|
||||
|
||||
StringAndFooBazString v1 = new StringAndFooBazString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
|
||||
StringAndFooBazString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
static class ReaderWriterWithMultipleAnnotationsJsonAdapter {
|
||||
@ToJson void fooPrefixAndBazSuffixStringToString(
|
||||
JsonWriter jsonWriter, @FooPrefix @BazSuffix String s) throws IOException {
|
||||
jsonWriter.value("foo" + s + "baz");
|
||||
}
|
||||
|
||||
@FromJson @FooPrefix @BazSuffix String fooPrefixAndBazSuffixStringFromString(
|
||||
JsonReader reader) throws Exception {
|
||||
String s = reader.nextString();
|
||||
if (!s.startsWith("foo")) throw new JsonDataException();
|
||||
if (!s.endsWith("baz")) throw new JsonDataException();
|
||||
return s.substring(3, s.length() - 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void basicTypesAnnotationDelegating() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new BuiltInTypesDelegatingJsonAdapter())
|
||||
.add(new BuiltInTypesJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<StringAndFooBazString> adapter = moshi.adapter(StringAndFooBazString.class);
|
||||
|
||||
StringAndFooBazString v1 = new StringAndFooBazString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
|
||||
StringAndFooBazString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
static class BuiltInTypesDelegatingJsonAdapter {
|
||||
@ToJson @FooPrefix String fooPrefixAndBazSuffixStringToString(@FooPrefix @BazSuffix String s) {
|
||||
return s + "baz";
|
||||
}
|
||||
|
||||
@FromJson @FooPrefix @BazSuffix String fooPrefixAndBazSuffixStringFromString(
|
||||
@FooPrefix String s) throws Exception {
|
||||
if (!s.endsWith("baz")) throw new JsonDataException();
|
||||
return s.substring(0, s.length() - 3);
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void readerWriterAnnotationDelegating() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new BuiltInTypesDelegatingJsonAdapter())
|
||||
.add(new ReaderWriterJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<StringAndFooBazString> adapter = moshi.adapter(StringAndFooBazString.class);
|
||||
|
||||
StringAndFooBazString v1 = new StringAndFooBazString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
|
||||
StringAndFooBazString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobarbaz\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test public void manualJsonAdapter() throws Exception {
|
||||
JsonAdapter<String> fooPrefixAdapter = new JsonAdapter<String>() {
|
||||
@Override public String fromJson(JsonReader reader) throws IOException {
|
||||
String s = reader.nextString();
|
||||
if (!s.startsWith("foo")) throw new JsonDataException();
|
||||
return s.substring(3);
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, String value) throws IOException {
|
||||
writer.value("foo" + value);
|
||||
}
|
||||
};
|
||||
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(String.class, FooPrefix.class, fooPrefixAdapter)
|
||||
.build();
|
||||
JsonAdapter<StringAndFooString> adapter = moshi.adapter(StringAndFooString.class);
|
||||
|
||||
StringAndFooString v1 = new StringAndFooString();
|
||||
v1.a = "aa";
|
||||
v1.b = "bar";
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":\"aa\",\"b\":\"foobar\"}");
|
||||
|
||||
StringAndFooString v2 = adapter.fromJson("{\"a\":\"aa\",\"b\":\"foobar\"}");
|
||||
assertThat(v2.a).isEqualTo("aa");
|
||||
assertThat(v2.b).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test public void noJsonAdapterForAnnotatedType() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
try {
|
||||
moshi.adapter(StringAndFooString.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void annotationWithoutJsonQualifierIsIgnoredByAdapterMethods() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new MissingJsonQualifierJsonAdapter())
|
||||
.build();
|
||||
JsonAdapter<DateAndMillisDate> adapter = moshi.adapter(DateAndMillisDate.class);
|
||||
|
||||
DateAndMillisDate v1 = new DateAndMillisDate();
|
||||
v1.a = new Date(5);
|
||||
v1.b = new Date(7);
|
||||
assertThat(adapter.toJson(v1)).isEqualTo("{\"a\":5,\"b\":7}");
|
||||
|
||||
DateAndMillisDate v2 = adapter.fromJson("{\"a\":5,\"b\":7}");
|
||||
assertThat(v2.a).isEqualTo(new Date(5));
|
||||
assertThat(v2.b).isEqualTo(new Date(7));
|
||||
}
|
||||
|
||||
/** Despite the fact that these methods are annotated, they match all dates. */
|
||||
static class MissingJsonQualifierJsonAdapter {
|
||||
@ToJson long dateToJson(@Millis Date d) {
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
@FromJson @Millis Date jsonToDate(long value) throws Exception {
|
||||
return new Date(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** This annotation does nothing. */
|
||||
@Retention(RUNTIME)
|
||||
public @interface Millis {
|
||||
}
|
||||
|
||||
static class DateAndMillisDate {
|
||||
Date a;
|
||||
@Millis Date b;
|
||||
}
|
||||
|
||||
@Test public void annotationWithoutJsonQualifierIsRejectedOnRegistration() throws Exception {
|
||||
JsonAdapter<Date> jsonAdapter = new JsonAdapter<Date>() {
|
||||
@Override public Date fromJson(JsonReader reader) throws IOException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override public void toJson(JsonWriter writer, Date value) throws IOException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
new Moshi.Builder().add(Date.class, Millis.class, jsonAdapter);
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected).hasMessage("interface com.squareup.moshi.JsonQualifiersTest$Millis "
|
||||
+ "does not have @JsonQualifier");
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void annotationsConflict() throws Exception {
|
||||
try {
|
||||
new Moshi.Builder().add(new AnnotationsConflictJsonAdapter());
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected).hasMessageContaining("Conflicting @ToJson methods");
|
||||
}
|
||||
}
|
||||
|
||||
static class AnnotationsConflictJsonAdapter {
|
||||
@ToJson String fooPrefixStringToString(@FooPrefix String s) {
|
||||
return "foo" + s;
|
||||
}
|
||||
|
||||
@ToJson String fooPrefixStringToString2(@FooPrefix String s) {
|
||||
return "foo" + s;
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void toButNoFromJson() throws Exception {
|
||||
// Building it is okay.
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new ToButNoFromJsonAdapter())
|
||||
.build();
|
||||
|
||||
try {
|
||||
moshi.adapter(StringAndFooString.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected).hasMessage("no JsonAdapter for class java.lang.String "
|
||||
+ "annotated [@com.squareup.moshi.JsonQualifiersTest$FooPrefix()]");
|
||||
}
|
||||
}
|
||||
|
||||
static class ToButNoFromJsonAdapter {
|
||||
@ToJson String fooPrefixStringToString(@FooPrefix String s) {
|
||||
return "foo" + s;
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void fromButNoToJson() throws Exception {
|
||||
// Building it is okay.
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new FromButNoToJsonAdapter())
|
||||
.build();
|
||||
|
||||
try {
|
||||
moshi.adapter(StringAndFooString.class);
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected).hasMessage("no JsonAdapter for class java.lang.String "
|
||||
+ "annotated [@com.squareup.moshi.JsonQualifiersTest$FooPrefix()]");
|
||||
}
|
||||
}
|
||||
|
||||
static class FromButNoToJsonAdapter {
|
||||
@FromJson @FooPrefix String fooPrefixStringFromString(String s) throws Exception {
|
||||
if (!s.startsWith("foo")) throw new JsonDataException();
|
||||
return s.substring(3);
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,8 +16,8 @@
|
||||
package com.squareup.moshi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayDeque;
|
||||
@@ -32,6 +32,7 @@ import org.junit.Test;
|
||||
import static com.squareup.moshi.TestUtil.newReader;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public final class MoshiTest {
|
||||
@@ -591,7 +592,8 @@ public final class MoshiTest {
|
||||
.add(new UppercaseAdapterFactory())
|
||||
.build();
|
||||
|
||||
AnnotatedElement annotations = MoshiTest.class.getDeclaredField("uppercaseString");
|
||||
Field uppercaseString = MoshiTest.class.getDeclaredField("uppercaseString");
|
||||
Set<? extends Annotation> annotations = Util.jsonAnnotations(uppercaseString);
|
||||
JsonAdapter<String> adapter = moshi.<String>adapter(String.class, annotations).lenient();
|
||||
assertThat(adapter.toJson("a")).isEqualTo("\"A\"");
|
||||
assertThat(adapter.fromJson("\"b\"")).isEqualTo("B");
|
||||
@@ -638,10 +640,14 @@ public final class MoshiTest {
|
||||
.build();
|
||||
|
||||
Field uppercaseStringsField = MoshiTest.class.getDeclaredField("uppercaseStrings");
|
||||
JsonAdapter<List<String>> adapter = moshi.adapter(uppercaseStringsField.getGenericType(),
|
||||
uppercaseStringsField);
|
||||
assertThat(adapter.toJson(Arrays.asList("a"))).isEqualTo("[\"a\"]");
|
||||
assertThat(adapter.fromJson("[\"b\"]")).isEqualTo(Arrays.asList("b"));
|
||||
try {
|
||||
moshi.adapter(uppercaseStringsField.getGenericType(),
|
||||
Util.jsonAnnotations(uppercaseStringsField));
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertEquals("no JsonAdapter for java.util.List<java.lang.String> annotated "
|
||||
+ "[@com.squareup.moshi.MoshiTest$Uppercase()]", expected.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void objectArray() throws Exception {
|
||||
@@ -753,9 +759,8 @@ public final class MoshiTest {
|
||||
|
||||
static class MealDealAdapterFactory implements JsonAdapter.Factory {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
Type type, Set<? extends Annotation> 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>() {
|
||||
@@ -778,16 +783,17 @@ public final class MoshiTest {
|
||||
}
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@JsonQualifier
|
||||
public @interface Uppercase {
|
||||
}
|
||||
|
||||
static class UppercaseAdapterFactory implements JsonAdapter.Factory {
|
||||
@Override public JsonAdapter<?> create(
|
||||
Type type, AnnotatedElement annotations, Moshi moshi) {
|
||||
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
|
||||
if (!type.equals(String.class)) return null;
|
||||
if (!annotations.isAnnotationPresent(Uppercase.class)) return null;
|
||||
if (!Util.isAnnotationPresent(annotations, Uppercase.class)) return null;
|
||||
|
||||
final JsonAdapter<String> stringAdapter = moshi.nextAdapter(this, String.class, annotations);
|
||||
final JsonAdapter<String> stringAdapter = moshi.nextAdapter(this, String.class);
|
||||
return new JsonAdapter<String>() {
|
||||
@Override public String fromJson(JsonReader reader) throws IOException {
|
||||
String s = stringAdapter.fromJson(reader);
|
||||
|
Reference in New Issue
Block a user