Add TypeAdapterFactory support to @JsonAdapter.

This commit is contained in:
Jake Wharton 2014-08-04 16:58:41 +00:00
parent 125e6d9d3d
commit 117d8ea68f
5 changed files with 103 additions and 17 deletions

View File

@ -16,13 +16,13 @@
package com.google.gson.annotations;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.google.gson.TypeAdapter;
/**
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class
* or field.
@ -87,6 +87,7 @@ import com.google.gson.TypeAdapter;
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface JsonAdapter {
Class<? extends TypeAdapter<?>> value();
/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}. */
Class<?> value();
}

View File

@ -21,7 +21,6 @@ import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.ObjectConstructor;
import com.google.gson.reflect.TypeToken;
/**
@ -41,8 +40,28 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class);
return annotation != null
? (TypeAdapter<T>) constructorConstructor.get(TypeToken.get(annotation.value())).construct()
: null;
if (annotation == null) {
return null;
}
return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
}
@SuppressWarnings("unchecked") // Casts guarded by conditionals.
static TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
TypeToken<?> fieldType, JsonAdapter annotation) {
Class<?> value = annotation.value();
if (TypeAdapter.class.isAssignableFrom(value)) {
Class<TypeAdapter<?>> typeAdapter = (Class<TypeAdapter<?>>) value;
return constructorConstructor.get(TypeToken.get(typeAdapter)).construct();
}
if (TypeAdapterFactory.class.isAssignableFrom(value)) {
Class<TypeAdapterFactory> typeAdapterFactory = (Class<TypeAdapterFactory>) value;
return constructorConstructor.get(TypeToken.get(typeAdapterFactory))
.construct()
.create(gson, fieldType);
}
throw new IllegalArgumentException(
"@JsonAdapter value must be TypeAdapter or TypeAdapterFactory reference.");
}
}

View File

@ -38,6 +38,8 @@ import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter;
/**
* Type adapter that reflects over the fields and methods of a class.
*/
@ -100,9 +102,11 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private TypeAdapter<?> getFieldAdapter(Gson gson, Field field, TypeToken<?> fieldType) {
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
return (annotation != null)
? constructorConstructor.get(TypeToken.get(annotation.value())).construct()
: gson.getAdapter(fieldType);
if (annotation != null) {
TypeAdapter<?> adapter = getTypeAdapter(constructorConstructor, gson, fieldType, annotation);
if (adapter != null) return adapter;
}
return gson.getAdapter(fieldType);
}
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {

View File

@ -16,11 +16,6 @@
package com.google.gson.functional;
import java.io.IOException;
import java.lang.reflect.Type;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
@ -31,9 +26,14 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import junit.framework.TestCase;
/**
* Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on classes.
@ -53,6 +53,14 @@ public final class JsonAdapterAnnotationOnClassesTest extends TestCase {
assertEquals("Leitch", user.lastName);
}
public void testJsonAdapterFactoryInvoked() {
Gson gson = new Gson();
String json = gson.toJson(new C("bar"));
assertEquals("\"jsonAdapterFactory\"", json);
C c = gson.fromJson("\"bar\"", C.class);
assertEquals("jsonAdapterFactory", c.value);
}
public void testRegisteredAdapterOverridesJsonAdapter() {
TypeAdapter<A> typeAdapter = new TypeAdapter<A>() {
@Override public void write(JsonWriter out, A value) throws IOException {
@ -125,7 +133,7 @@ public final class JsonAdapterAnnotationOnClassesTest extends TestCase {
A(String value) {
this.value = value;
}
private static final class JsonAdapter extends TypeAdapter<A> {
static final class JsonAdapter extends TypeAdapter<A> {
@Override public void write(JsonWriter out, A value) throws IOException {
out.value("jsonAdapter");
}
@ -136,6 +144,27 @@ public final class JsonAdapterAnnotationOnClassesTest extends TestCase {
}
}
@JsonAdapter(C.JsonAdapterFactory.class)
private static class C {
final String value;
C(String value) {
this.value = value;
}
static final class JsonAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException {
out.value("jsonAdapterFactory");
}
@Override public T read(JsonReader in) throws IOException {
in.nextString();
return (T) new C("jsonAdapterFactory");
}
};
}
}
}
private static final class B extends A {
B(String value) {
super(value);

View File

@ -19,7 +19,9 @@ package com.google.gson.functional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
@ -37,6 +39,14 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
assertEquals("UserClassAnnotationAdapter", computer.user.name);
}
public void testClassAnnotationAdapterFactoryTakesPrecedenceOverDefault() {
Gson gson = new Gson();
String json = gson.toJson(new Gizmo(new Part("Part")));
assertEquals("{\"part\":\"GizmoPartTypeAdapterFactory\"}", json);
Gizmo computer = gson.fromJson("{'part':'Part'}", Gizmo.class);
assertEquals("GizmoPartTypeAdapterFactory", computer.part.name);
}
public void testRegisteredTypeAdapterTakesPrecedenceOverClassAnnotationAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new RegisteredUserAdapter())
@ -80,9 +90,17 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
}
}
private static final class Gizmo {
@JsonAdapter(GizmoPartTypeAdapterFactory.class)
final Part part;
Gizmo(Part part) {
this.part = part;
}
}
private static final class Part {
final String name;
Part(String name) {
public Part(String name) {
this.name = name;
}
}
@ -97,6 +115,21 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
}
}
private static class GizmoPartTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
return new TypeAdapter<T>() {
@Override public void write(JsonWriter out, T value) throws IOException {
out.value("GizmoPartTypeAdapterFactory");
}
@SuppressWarnings("unchecked")
@Override public T read(JsonReader in) throws IOException {
in.nextString();
return (T) new Part("GizmoPartTypeAdapterFactory");
}
};
}
}
private static final class Computer {
final User user;
Computer(User user) {