mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Merge pull request #197 from square/jwilson.1013.json_adapter_parameters
Permit JsonAdapters to be injected in toJson(), fromJson() methods.
This commit is contained in:
@@ -19,11 +19,14 @@ import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.squareup.moshi.Util.jsonAnnotations;
|
||||
|
||||
final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
private final List<AdapterMethod> toAdapters;
|
||||
private final List<AdapterMethod> fromAdapters;
|
||||
@@ -52,6 +55,9 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
delegate = null;
|
||||
}
|
||||
|
||||
if (toAdapter != null) toAdapter.bind(moshi);
|
||||
if (fromAdapter != null) fromAdapter.bind(moshi);
|
||||
|
||||
return new JsonAdapter<Object>() {
|
||||
@Override public void toJson(JsonWriter writer, Object value) throws IOException {
|
||||
if (toAdapter == null) {
|
||||
@@ -61,8 +67,6 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
} else {
|
||||
try {
|
||||
toAdapter.toJson(moshi, writer, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof IOException) throw (IOException) cause;
|
||||
@@ -80,8 +84,6 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
} else {
|
||||
try {
|
||||
return fromAdapter.fromJson(moshi, reader);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof IOException) throw (IOException) cause;
|
||||
@@ -140,34 +142,42 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
*/
|
||||
static AdapterMethod toAdapter(Object adapter, Method method) {
|
||||
method.setAccessible(true);
|
||||
Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
final Type returnType = method.getGenericReturnType();
|
||||
final Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
|
||||
if (parameterTypes.length == 2
|
||||
if (parameterTypes.length >= 2
|
||||
&& parameterTypes[0] == JsonWriter.class
|
||||
&& returnType == void.class) {
|
||||
// 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, false) {
|
||||
&& returnType == void.class
|
||||
&& parametersAreJsonAdapters(2, parameterTypes)) {
|
||||
// void pointToJson(JsonWriter jsonWriter, Point point) {
|
||||
// void pointToJson(JsonWriter jsonWriter, Point point, JsonAdapter<?> adapter, ...) {
|
||||
Set<? extends Annotation> qualifierAnnotations = jsonAnnotations(parameterAnnotations[1]);
|
||||
return new AdapterMethod(parameterTypes[1], qualifierAnnotations, adapter, method,
|
||||
parameterTypes.length, 2, false) {
|
||||
@Override public void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, InvocationTargetException, IllegalAccessException {
|
||||
method.invoke(adapter, writer, value);
|
||||
throws IOException, InvocationTargetException {
|
||||
invoke(writer, value);
|
||||
}
|
||||
};
|
||||
|
||||
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
||||
// public List<Integer> pointToJson(Point point) throws Exception {
|
||||
final Set<? extends Annotation> returnTypeAnnotations = Util.jsonAnnotations(method);
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
Set<? extends Annotation> qualifierAnnotations =
|
||||
Util.jsonAnnotations(parameterAnnotations[0]);
|
||||
// List<Integer> pointToJson(Point point) {
|
||||
final Set<? extends Annotation> returnTypeAnnotations = jsonAnnotations(method);
|
||||
Set<? extends Annotation> qualifierAnnotations = jsonAnnotations(parameterAnnotations[0]);
|
||||
boolean nullable = Util.hasNullable(parameterAnnotations[0]);
|
||||
return new AdapterMethod(parameterTypes[0], qualifierAnnotations, adapter, method, nullable) {
|
||||
return new AdapterMethod(parameterTypes[0], qualifierAnnotations, adapter, method,
|
||||
parameterTypes.length, 1, nullable) {
|
||||
private JsonAdapter<Object> delegate;
|
||||
|
||||
@Override public void bind(Moshi moshi) {
|
||||
super.bind(moshi);
|
||||
delegate = moshi.adapter(returnType, returnTypeAnnotations);
|
||||
}
|
||||
|
||||
@Override public void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, InvocationTargetException, IllegalAccessException {
|
||||
JsonAdapter<Object> delegate = moshi.adapter(returnType, returnTypeAnnotations);
|
||||
Object intermediate = method.invoke(adapter, value);
|
||||
throws IOException, InvocationTargetException {
|
||||
Object intermediate = invoke(value);
|
||||
delegate.toJson(writer, intermediate);
|
||||
}
|
||||
};
|
||||
@@ -180,40 +190,58 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if {@code parameterTypes[offset..]} contains only JsonAdapters. */
|
||||
private static boolean parametersAreJsonAdapters(int offset, Type[] parameterTypes) {
|
||||
for (int i = offset, length = parameterTypes.length; i < length; i++) {
|
||||
if (!(parameterTypes[i] instanceof ParameterizedType)) return false;
|
||||
if (((ParameterizedType) parameterTypes[i]).getRawType() != JsonAdapter.class) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that calls a {@code method} method on {@code adapter} in service of
|
||||
* converting an object from JSON.
|
||||
*/
|
||||
static AdapterMethod fromAdapter(Object adapter, Method method) {
|
||||
method.setAccessible(true);
|
||||
final Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
final Type returnType = method.getGenericReturnType();
|
||||
Set<? extends Annotation> returnTypeAnnotations = jsonAnnotations(method);
|
||||
final Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
|
||||
if (parameterTypes.length == 1
|
||||
if (parameterTypes.length >= 1
|
||||
&& parameterTypes[0] == JsonReader.class
|
||||
&& returnType != void.class) {
|
||||
// public Point pointFromJson(JsonReader jsonReader) throws Exception {
|
||||
Set<? extends Annotation> returnTypeAnnotations = Util.jsonAnnotations(method);
|
||||
return new AdapterMethod(returnType, returnTypeAnnotations, adapter, method, false) {
|
||||
&& returnType != void.class
|
||||
&& parametersAreJsonAdapters(1, parameterTypes)) {
|
||||
// Point pointFromJson(JsonReader jsonReader) {
|
||||
// Point pointFromJson(JsonReader jsonReader, JsonAdapter<?> adapter, ...) {
|
||||
return new AdapterMethod(returnType, returnTypeAnnotations, adapter, method,
|
||||
parameterTypes.length, 1, false) {
|
||||
@Override public Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
return method.invoke(adapter, reader);
|
||||
throws IOException, InvocationTargetException {
|
||||
return invoke(reader);
|
||||
}
|
||||
};
|
||||
|
||||
} else if (parameterTypes.length == 1 && returnType != void.class) {
|
||||
// public Point pointFromJson(List<Integer> o) throws Exception {
|
||||
Set<? extends Annotation> returnTypeAnnotations = Util.jsonAnnotations(method);
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
// Point pointFromJson(List<Integer> o) {
|
||||
final Set<? extends Annotation> qualifierAnnotations
|
||||
= Util.jsonAnnotations(parameterAnnotations[0]);
|
||||
= jsonAnnotations(parameterAnnotations[0]);
|
||||
boolean nullable = Util.hasNullable(parameterAnnotations[0]);
|
||||
return new AdapterMethod(returnType, returnTypeAnnotations, adapter, method, nullable) {
|
||||
return new AdapterMethod(returnType, returnTypeAnnotations, adapter, method,
|
||||
parameterTypes.length, 1, nullable) {
|
||||
JsonAdapter<Object> delegate;
|
||||
|
||||
@Override public void bind(Moshi moshi) {
|
||||
super.bind(moshi);
|
||||
delegate = moshi.adapter(parameterTypes[0], qualifierAnnotations);
|
||||
}
|
||||
|
||||
@Override public Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
JsonAdapter<Object> delegate = moshi.adapter(parameterTypes[0], qualifierAnnotations);
|
||||
throws IOException, InvocationTargetException {
|
||||
Object intermediate = delegate.fromJson(reader);
|
||||
return method.invoke(adapter, intermediate);
|
||||
return invoke(intermediate);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -242,25 +270,68 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
|
||||
final Set<? extends Annotation> annotations;
|
||||
final Object adapter;
|
||||
final Method method;
|
||||
final int adaptersOffset;
|
||||
final JsonAdapter<?>[] jsonAdapters;
|
||||
final boolean nullable;
|
||||
|
||||
public AdapterMethod(Type type,
|
||||
Set<? extends Annotation> annotations, Object adapter, Method method, boolean nullable) {
|
||||
public AdapterMethod(Type type, Set<? extends Annotation> annotations, Object adapter,
|
||||
Method method, int parameterCount, int adaptersOffset, boolean nullable) {
|
||||
this.type = Types.canonicalize(type);
|
||||
this.annotations = annotations;
|
||||
this.adapter = adapter;
|
||||
this.method = method;
|
||||
this.adaptersOffset = adaptersOffset;
|
||||
this.jsonAdapters = new JsonAdapter[parameterCount - adaptersOffset];
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
public void bind(Moshi moshi) {
|
||||
if (jsonAdapters.length > 0) {
|
||||
Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
|
||||
for (int i = adaptersOffset, size = parameterTypes.length; i < size; i++) {
|
||||
jsonAdapters[i - adaptersOffset] = moshi.adapter(
|
||||
((ParameterizedType) parameterTypes[i]).getActualTypeArguments()[0],
|
||||
jsonAnnotations(parameterAnnotations[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void toJson(Moshi moshi, JsonWriter writer, Object value)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
throws IOException, InvocationTargetException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public Object fromJson(Moshi moshi, JsonReader reader)
|
||||
throws IOException, IllegalAccessException, InvocationTargetException {
|
||||
throws IOException, InvocationTargetException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/** Invoke the method with one fixed argument, plus any number of JSON adapter arguments. */
|
||||
protected Object invoke(Object a1) throws InvocationTargetException {
|
||||
Object[] args = new Object[1 + jsonAdapters.length];
|
||||
args[0] = a1;
|
||||
System.arraycopy(jsonAdapters, 0, args, 1, jsonAdapters.length);
|
||||
|
||||
try {
|
||||
return method.invoke(adapter, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/** Invoke the method with two fixed arguments, plus any number of JSON adapter arguments. */
|
||||
protected Object invoke(Object a1, Object a2) throws InvocationTargetException {
|
||||
Object[] args = new Object[2 + jsonAdapters.length];
|
||||
args[0] = a1;
|
||||
args[1] = a2;
|
||||
System.arraycopy(jsonAdapters, 0, args, 2, jsonAdapters.length);
|
||||
|
||||
try {
|
||||
return method.invoke(adapter, args);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -406,6 +406,66 @@ public final class AdapterMethodsTest {
|
||||
assertThat(moshi.adapter(c)).isSameAs(moshi.adapter(a));
|
||||
}
|
||||
|
||||
@Test public void writerAndReaderTakingJsonAdapterParameter() throws Exception {
|
||||
Moshi moshi = new Moshi.Builder()
|
||||
.add(new PointWriterAndReaderJsonAdapter())
|
||||
.add(new JsonAdapterWithWriterAndReaderTakingJsonAdapterParameter())
|
||||
.build();
|
||||
JsonAdapter<Line> lineAdapter = moshi.adapter(Line.class);
|
||||
Line line = new Line(new Point(5, 8), new Point(3, 2));
|
||||
assertThat(lineAdapter.toJson(line)).isEqualTo("[[5,8],[3,2]]");
|
||||
assertThat(lineAdapter.fromJson("[[5,8],[3,2]]")).isEqualTo(line);
|
||||
}
|
||||
|
||||
static class JsonAdapterWithWriterAndReaderTakingJsonAdapterParameter {
|
||||
@ToJson void lineToJson(
|
||||
JsonWriter writer, Line line, JsonAdapter<Point> pointAdapter) throws IOException {
|
||||
writer.beginArray();
|
||||
pointAdapter.toJson(writer, line.a);
|
||||
pointAdapter.toJson(writer, line.b);
|
||||
writer.endArray();
|
||||
}
|
||||
|
||||
@FromJson Line lineFromJson(
|
||||
JsonReader reader, JsonAdapter<Point> pointAdapter) throws Exception {
|
||||
reader.beginArray();
|
||||
Point a = pointAdapter.fromJson(reader);
|
||||
Point b = pointAdapter.fromJson(reader);
|
||||
reader.endArray();
|
||||
return new Line(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void noToJsonAdapterTakingJsonAdapterParameter() throws Exception {
|
||||
try {
|
||||
new Moshi.Builder().add(new ToJsonAdapterTakingJsonAdapterParameter());
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected).hasMessageStartingWith("Unexpected signature");
|
||||
}
|
||||
}
|
||||
|
||||
static class ToJsonAdapterTakingJsonAdapterParameter {
|
||||
@ToJson String lineToJson(Line line, JsonAdapter<Point> pointAdapter) throws IOException {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void noFromJsonAdapterTakingJsonAdapterParameter() throws Exception {
|
||||
try {
|
||||
new Moshi.Builder().add(new FromJsonAdapterTakingJsonAdapterParameter());
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertThat(expected).hasMessageStartingWith("Unexpected signature");
|
||||
}
|
||||
}
|
||||
|
||||
static class FromJsonAdapterTakingJsonAdapterParameter {
|
||||
@FromJson Line lineFromJson(String value, JsonAdapter<Point> pointAdapter) throws Exception {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
static class Point {
|
||||
final int x;
|
||||
final int y;
|
||||
@@ -424,6 +484,24 @@ public final class AdapterMethodsTest {
|
||||
}
|
||||
}
|
||||
|
||||
static class Line {
|
||||
final Point a;
|
||||
final Point b;
|
||||
|
||||
public Line(Point a, Point b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof Line && ((Line) o).a.equals(a) && ((Line) o).b.equals(b);
|
||||
}
|
||||
|
||||
@Override public int hashCode() {
|
||||
return a.hashCode() * 37 + b.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
interface Shape {
|
||||
String draw();
|
||||
}
|
||||
|
Reference in New Issue
Block a user