Skip to content

Commit

Permalink
Add TypeAdapterFactory support to @JsonAdapter.
Browse files Browse the repository at this point in the history
  • Loading branch information
jw@squareup.com committed Aug 4, 2014
1 parent 225a6c3 commit 29bbc9b
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -87,6 +87,7 @@
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface JsonAdapter {

Class<? extends TypeAdapter<?>> value();
/** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}. */
Class<?> value();

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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;

/**
Expand All @@ -41,8 +40,28 @@ public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructo
@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.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
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.
*/
Expand Down Expand Up @@ -100,9 +102,11 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,9 +26,14 @@
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.
Expand All @@ -53,6 +53,14 @@ public void testJsonAdapterInvoked() {
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 {
Expand Down Expand Up @@ -125,7 +133,7 @@ private static class A {
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");
}
Expand All @@ -136,6 +144,27 @@ private static final class JsonAdapter extends TypeAdapter<A> {
}
}

@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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
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;
Expand All @@ -37,6 +39,14 @@ public void testClassAnnotationAdapterTakesPrecedenceOverDefault() {
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())
Expand Down Expand Up @@ -80,9 +90,17 @@ private static final class Gadget {
}
}

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;
}
}
Expand All @@ -97,6 +115,21 @@ private static class PartJsonFieldAnnotationAdapter extends TypeAdapter<Part> {
}
}

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) {
Expand Down

0 comments on commit 29bbc9b

Please sign in to comment.