Change field annotations to take precedence over registered type adapters.

This commit is contained in:
Jesse Wilson 2014-08-02 18:22:43 +00:00
parent f9a302e22a
commit 125e6d9d3d
5 changed files with 76 additions and 123 deletions

View File

@ -49,10 +49,8 @@ import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* This is the main class for using Gson. Gson is typically used by first constructing a * This is the main class for using Gson. Gson is typically used by first constructing a
@ -115,15 +113,6 @@ public final class Gson {
private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache
= Collections.synchronizedMap(new HashMap<TypeToken<?>, TypeAdapter<?>>()); = Collections.synchronizedMap(new HashMap<TypeToken<?>, TypeAdapter<?>>());
/** Indicates whether Gson is in the phase of constructor invocation. It is used to determine
* whether to add a constructor in preconfiguredGeneratedTypeAdapter set or not. */
private boolean inConstructorPhase = true;
/** List of type adapters that are generated by Gson during its constructor */
private Set<TypeAdapter<?>> preconfiguredGeneratedTypeAdapters = new HashSet<TypeAdapter<?>>();
/** List of type adapters that are generated by Gson during toJson/fromJson. */
private final ThreadLocal<Set<TypeAdapter<?>>> runtimeGeneratedTypeAdapters =
new ThreadLocal<Set<TypeAdapter<?>>>();
private final List<TypeAdapterFactory> factories; private final List<TypeAdapterFactory> factories;
private final ConstructorConstructor constructorConstructor; private final ConstructorConstructor constructorConstructor;
@ -254,8 +243,6 @@ public final class Gson {
constructorConstructor, fieldNamingPolicy, excluder)); constructorConstructor, fieldNamingPolicy, excluder));
this.factories = Collections.unmodifiableList(factories); this.factories = Collections.unmodifiableList(factories);
this.preconfiguredGeneratedTypeAdapters = Collections.unmodifiableSet(preconfiguredGeneratedTypeAdapters);
inConstructorPhase = false;
} }
private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) { private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
@ -917,26 +904,4 @@ public final class Gson {
.append("}") .append("}")
.toString(); .toString();
} }
public static final class $$Internal {
public static void addGeneratedTypeAdapter(Gson gson, TypeAdapter<?> typeAdapter) {
if (gson.inConstructorPhase) {
gson.preconfiguredGeneratedTypeAdapters.add(typeAdapter);
} else {
Set<TypeAdapter<?>> adapters = getRuntimeGeneratedTypeAdapters(gson);
adapters.add(typeAdapter);
}
}
public static boolean isGeneratedTypeAdapter(Gson gson, TypeAdapter<?> typeAdapter) {
boolean generated = gson.preconfiguredGeneratedTypeAdapters.contains(typeAdapter);
if (!generated) generated = getRuntimeGeneratedTypeAdapters(gson).contains(typeAdapter);
return generated;
}
private static Set<TypeAdapter<?>> getRuntimeGeneratedTypeAdapters(Gson gson) {
Set<TypeAdapter<?>> adapters = gson.runtimeGeneratedTypeAdapters.get();
if (adapters == null) adapters = new HashSet<TypeAdapter<?>>();
gson.runtimeGeneratedTypeAdapters.set(adapters);
return adapters;
}
}
} }

View File

@ -24,9 +24,8 @@ import java.lang.annotation.Target;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
/** /**
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class or a field. * An annotation that indicates the Gson {@link TypeAdapter} to use with a class
* Any type adapters registered in {@link com.google.gson.GsonBuilder} supersede the adapter * or field.
* specified in this annotation.
* *
* <p>Here is an example of how this annotation is used:</p> * <p>Here is an example of how this annotation is used:</p>
* <pre> * <pre>
@ -61,9 +60,6 @@ import com.google.gson.TypeAdapter;
* Since User class specified UserJsonAdapter.class in &#64JsonAdapter annotation, it * Since User class specified UserJsonAdapter.class in &#64JsonAdapter annotation, it
* will automatically be invoked to serialize/deserialize User instances. <br> * will automatically be invoked to serialize/deserialize User instances. <br>
* *
* If the UserJsonAdapter needs a constructor other than a no-args constructor, you must register
* an {@link com.google.gson.InstanceCreator} for it.
*
* <p> Here is an example of how to apply this annotation to a field. * <p> Here is an example of how to apply this annotation to a field.
* <pre> * <pre>
* private static final class Gadget { * private static final class Gadget {
@ -74,8 +70,11 @@ import com.google.gson.TypeAdapter;
* } * }
* } * }
* </pre> * </pre>
* The above annotation will ensure UserJsonAdapter2 takes precedence over UserJsonAdapter *
* for the user field of the Gadget class. * It's possible to specify different type adapters on a field, that
* field's type, and in the {@link com.google.gson.GsonBuilder}. Field
* annotations take precedence over {@code GsonBuilder}-registered type
* adapters, which in turn take precedence over annotated types.
* *
* @since 2.3 * @since 2.3
* *

View File

@ -38,22 +38,11 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
this.constructorConstructor = constructorConstructor; this.constructorConstructor = constructorConstructor;
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({"rawtypes", "unchecked"})
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
Class<? super T> clazz = targetType.getRawType(); JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class);
JsonAdapter annotation = clazz.getAnnotation(JsonAdapter.class); return annotation != null
if (annotation == null) return null; ? (TypeAdapter<T>) constructorConstructor.get(TypeToken.get(annotation.value())).construct()
TypeAdapter adapter = getAnnotationTypeAdapter(gson, constructorConstructor, annotation); : null;
return adapter;
}
static TypeAdapter<?> getAnnotationTypeAdapter(Gson gson,
ConstructorConstructor constructorConstructor, JsonAdapter annotation) {
Class<? extends TypeAdapter<?>> adapterClass = annotation.value();
ObjectConstructor<? extends TypeAdapter<?>> constructor =
constructorConstructor.get(TypeToken.get(adapterClass));
TypeAdapter<?> adapter = constructor.construct();
Gson.$$Internal.addGeneratedTypeAdapter(gson, adapter);
return adapter;
} }
} }

View File

@ -16,12 +16,6 @@
package com.google.gson.internal.bind; package com.google.gson.internal.bind;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.gson.FieldNamingStrategy; import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
@ -38,6 +32,11 @@ import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
/** /**
* Type adapter that reflects over the fields and methods of a class. * Type adapter that reflects over the fields and methods of a class.
@ -71,9 +70,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
ObjectConstructor<T> constructor = constructorConstructor.get(type); ObjectConstructor<T> constructor = constructorConstructor.get(type);
Adapter<T> adapter = new Adapter<T>(constructor, getBoundFields(gson, type, raw)); return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
Gson.$$Internal.addGeneratedTypeAdapter(gson, adapter);
return adapter;
} }
private ReflectiveTypeAdapterFactory.BoundField createBoundField( private ReflectiveTypeAdapterFactory.BoundField createBoundField(
@ -102,14 +99,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
} }
private TypeAdapter<?> getFieldAdapter(Gson gson, Field field, TypeToken<?> fieldType) { private TypeAdapter<?> getFieldAdapter(Gson gson, Field field, TypeToken<?> fieldType) {
TypeAdapter<?> adapter = gson.getAdapter(fieldType); JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
boolean generatedAdapter = Gson.$$Internal.isGeneratedTypeAdapter(gson, adapter); return (annotation != null)
if (generatedAdapter && field.isAnnotationPresent(JsonAdapter.class)) { ? constructorConstructor.get(TypeToken.get(annotation.value())).construct()
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class); : gson.getAdapter(fieldType);
return JsonAdapterAnnotationTypeAdapterFactory.getAnnotationTypeAdapter(
gson, constructorConstructor, annotation);
}
return adapter;
} }
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) { private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {

View File

@ -16,56 +16,64 @@
package com.google.gson.functional; package com.google.gson.functional;
import java.io.IOException;
import junit.framework.TestCase;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import junit.framework.TestCase;
/** /**
* Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on fields. * Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on fields.
*/ */
public final class JsonAdapterAnnotationOnFieldsTest extends TestCase { public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
public void testClassAnnotationAdapterTakesPrecedenceOverDefault() {
public void testJsonAdapterInvoked() {
Gson gson = new Gson(); Gson gson = new Gson();
String json = gson.toJson(new Computer(new User("Inderjeet Singh"))); String json = gson.toJson(new Computer(new User("Inderjeet Singh")));
assertEquals("{\"user\":{\"firstName\":\"Inderjeet\",\"lastName\":\"Singh\"}}", json); assertEquals("{\"user\":\"UserClassAnnotationAdapter\"}", json);
Computer computer = gson.fromJson("{'user':{'firstName':'Jesse','lastName':'Wilson'}}", Computer.class); Computer computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer.class);
assertEquals("Jesse Wilson", computer.user.name); assertEquals("UserClassAnnotationAdapter", computer.user.name);
} }
public void testRegisteredTypeAdapterOverridesFieldAnnotation() { public void testRegisteredTypeAdapterTakesPrecedenceOverClassAnnotationAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(User.class, new RegisteredUserAdapter())
.create();
String json = gson.toJson(new Computer(new User("Inderjeet Singh")));
assertEquals("{\"user\":\"RegisteredUserAdapter\"}", json);
Computer computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer.class);
assertEquals("RegisteredUserAdapter", computer.user.name);
}
public void testFieldAnnotationTakesPrecedenceOverRegisteredTypeAdapter() {
Gson gson = new GsonBuilder() Gson gson = new GsonBuilder()
.registerTypeAdapter(Part.class, new TypeAdapter<Part>() { .registerTypeAdapter(Part.class, new TypeAdapter<Part>() {
@Override public void write(JsonWriter out, Part part) throws IOException { @Override public void write(JsonWriter out, Part part) throws IOException {
out.value("registeredAdapter"); throw new AssertionError();
} }
@Override public Part read(JsonReader in) throws IOException { @Override public Part read(JsonReader in) throws IOException {
return new Part(in.nextString()); throw new AssertionError();
} }
}).create(); }).create();
String json = gson.toJson(new Gadget(new Part("screen"))); String json = gson.toJson(new Gadget(new Part("screen")));
assertEquals("{\"part\":\"registeredAdapter\"}", json); assertEquals("{\"part\":\"PartJsonFieldAnnotationAdapter\"}", json);
Gadget gadget = gson.fromJson("{'part':'registeredAdapterValue'}", Gadget.class); Gadget gadget = gson.fromJson("{'part':'screen'}", Gadget.class);
assertEquals("registeredAdapterValue", gadget.part.name); assertEquals("PartJsonFieldAnnotationAdapter", gadget.part.name);
} }
public void testFieldAnnotationSupersedesClassAnnotation() { public void testFieldAnnotationTakesPrecedenceOverClassAnnotation() {
Gson gson = new Gson(); Gson gson = new Gson();
String json = gson.toJson(new Computer2(new User("Inderjeet Singh"))); String json = gson.toJson(new Computer2(new User("Inderjeet Singh")));
assertEquals("{\"user\":\"userJsonAdapter2\"}", json); assertEquals("{\"user\":\"UserFieldAnnotationAdapter\"}", json);
Computer2 target = gson.fromJson("{'user':'userJsonAdapter2Value'}", Computer2.class); Computer2 target = gson.fromJson("{'user':'Interjeet Singh'}", Computer2.class);
assertEquals("userJsonAdapter2Value", target.user.name); assertEquals("UserFieldAnnotationAdapter", target.user.name);
} }
private static final class Gadget { private static final class Gadget {
@JsonAdapter(PartJsonAdapter.class) @JsonAdapter(PartJsonFieldAnnotationAdapter.class)
final Part part; final Part part;
Gadget(Part part) { Gadget(Part part) {
this.part = part; this.part = part;
@ -79,13 +87,13 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
} }
} }
private static class PartJsonAdapter extends TypeAdapter<Part> { private static class PartJsonFieldAnnotationAdapter extends TypeAdapter<Part> {
@Override public void write(JsonWriter out, Part part) throws IOException { @Override public void write(JsonWriter out, Part part) throws IOException {
out.value(part.name); out.value("PartJsonFieldAnnotationAdapter");
} }
@Override public Part read(JsonReader in) throws IOException { @Override public Part read(JsonReader in) throws IOException {
in.nextString(); in.nextString();
return new Part("partJsonAdapter"); return new Part("PartJsonFieldAnnotationAdapter");
} }
} }
@ -96,7 +104,7 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
} }
} }
@JsonAdapter(UserJsonAdapter.class) @JsonAdapter(UserClassAnnotationAdapter.class)
private static class User { private static class User {
public final String name; public final String name;
private User(String name) { private User(String name) {
@ -104,43 +112,42 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
} }
} }
private static class UserJsonAdapter extends TypeAdapter<User> { private static class UserClassAnnotationAdapter extends TypeAdapter<User> {
@Override public void write(JsonWriter out, User user) throws IOException { @Override public void write(JsonWriter out, User user) throws IOException {
// implement write: combine firstName and lastName into name out.value("UserClassAnnotationAdapter");
out.beginObject();
String[] parts = user.name.split(" ");
out.name("firstName");
out.value(parts[0]);
out.name("lastName");
out.value(parts[1]);
out.endObject();
} }
@Override public User read(JsonReader in) throws IOException { @Override public User read(JsonReader in) throws IOException {
// implement read: split name into firstName and lastName in.nextString();
in.beginObject(); return new User("UserClassAnnotationAdapter");
in.nextName();
String firstName = in.nextString();
in.nextName();
String lastName = in.nextString();
in.endObject();
return new User(firstName + " " + lastName);
} }
} }
private static final class Computer2 { private static final class Computer2 {
// overrides the JsonAdapter annotation of User with this // overrides the JsonAdapter annotation of User with this
@JsonAdapter(UserJsonAdapter2.class) @JsonAdapter(UserFieldAnnotationAdapter.class)
final User user; final User user;
Computer2(User user) { Computer2(User user) {
this.user = user; this.user = user;
} }
} }
private static final class UserJsonAdapter2 extends TypeAdapter<User> {
private static final class UserFieldAnnotationAdapter extends TypeAdapter<User> {
@Override public void write(JsonWriter out, User user) throws IOException { @Override public void write(JsonWriter out, User user) throws IOException {
out.value("userJsonAdapter2"); out.value("UserFieldAnnotationAdapter");
} }
@Override public User read(JsonReader in) throws IOException { @Override public User read(JsonReader in) throws IOException {
return new User(in.nextString()); in.nextString();
return new User("UserFieldAnnotationAdapter");
}
}
private static final class RegisteredUserAdapter extends TypeAdapter<User> {
@Override public void write(JsonWriter out, User user) throws IOException {
out.value("RegisteredUserAdapter");
}
@Override public User read(JsonReader in) throws IOException {
in.nextString();
return new User("RegisteredUserAdapter");
} }
} }
@ -148,12 +155,12 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
Gson gson = new Gson(); Gson gson = new Gson();
String json = "{'part1':'name','part2':{'name':'name2'}}"; String json = "{'part1':'name','part2':{'name':'name2'}}";
GadgetWithTwoParts gadget = gson.fromJson(json, GadgetWithTwoParts.class); GadgetWithTwoParts gadget = gson.fromJson(json, GadgetWithTwoParts.class);
assertEquals("partJsonAdapter", gadget.part1.name); assertEquals("PartJsonFieldAnnotationAdapter", gadget.part1.name);
assertEquals("name2", gadget.part2.name); assertEquals("name2", gadget.part2.name);
} }
private static final class GadgetWithTwoParts { private static final class GadgetWithTwoParts {
@JsonAdapter(PartJsonAdapter.class) final Part part1; @JsonAdapter(PartJsonFieldAnnotationAdapter.class) final Part part1;
final Part part2; // Doesn't have the JsonAdapter annotation final Part part2; // Doesn't have the JsonAdapter annotation
@SuppressWarnings("unused") GadgetWithTwoParts(Part part1, Part part2) { @SuppressWarnings("unused") GadgetWithTwoParts(Part part1, Part part2) {
this.part1 = part1; this.part1 = part1;