From 117d8ea68ff953a271c492736ba3eed3bb209da2 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Mon, 4 Aug 2014 16:58:41 +0000 Subject: [PATCH] Add TypeAdapterFactory support to @JsonAdapter. --- .../google/gson/annotations/JsonAdapter.java | 7 ++-- ...onAdapterAnnotationTypeAdapterFactory.java | 27 ++++++++++-- .../bind/ReflectiveTypeAdapterFactory.java | 10 +++-- .../JsonAdapterAnnotationOnClassesTest.java | 41 ++++++++++++++++--- .../JsonAdapterAnnotationOnFieldsTest.java | 35 +++++++++++++++- 5 files changed, 103 insertions(+), 17 deletions(-) diff --git a/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java index 85525098..f138545d 100644 --- a/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java +++ b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java @@ -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> value(); + /** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}. */ + Class value(); } diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java index 189da717..02b5b6de 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java @@ -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 TypeAdapter create(Gson gson, TypeToken targetType) { JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class); - return annotation != null - ? (TypeAdapter) constructorConstructor.get(TypeToken.get(annotation.value())).construct() - : null; + if (annotation == null) { + return null; + } + return (TypeAdapter) 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 = (Class>) value; + return constructorConstructor.get(TypeToken.get(typeAdapter)).construct(); + } + if (TypeAdapterFactory.class.isAssignableFrom(value)) { + Class typeAdapterFactory = (Class) value; + return constructorConstructor.get(TypeToken.get(typeAdapterFactory)) + .construct() + .create(gson, fieldType); + } + + throw new IllegalArgumentException( + "@JsonAdapter value must be TypeAdapter or TypeAdapterFactory reference."); } } diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index eac09955..e87a490f 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -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 getBoundFields(Gson context, TypeToken type, Class raw) { diff --git a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java index bae36575..b3ada137 100644 --- a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java +++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java @@ -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 typeAdapter = new TypeAdapter() { @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 { + static final class JsonAdapter extends TypeAdapter { @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 TypeAdapter create(Gson gson, final TypeToken type) { + return new TypeAdapter() { + @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); diff --git a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java index fdfaae54..d3f097ea 100644 --- a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java +++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java @@ -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 TypeAdapter create(Gson gson, final TypeToken type) { + return new TypeAdapter() { + @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) {