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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 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
= 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 ConstructorConstructor constructorConstructor;
@ -254,8 +243,6 @@ public final class Gson {
constructorConstructor, fieldNamingPolicy, excluder));
this.factories = Collections.unmodifiableList(factories);
this.preconfiguredGeneratedTypeAdapters = Collections.unmodifiableSet(preconfiguredGeneratedTypeAdapters);
inConstructorPhase = false;
}
private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
@ -917,26 +904,4 @@ public final class Gson {
.append("}")
.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;
/**
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class or a field.
* Any type adapters registered in {@link com.google.gson.GsonBuilder} supersede the adapter
* specified in this annotation.
* An annotation that indicates the Gson {@link TypeAdapter} to use with a class
* or field.
*
* <p>Here is an example of how this annotation is used:</p>
* <pre>
@ -61,9 +60,6 @@ import com.google.gson.TypeAdapter;
* Since User class specified UserJsonAdapter.class in &#64JsonAdapter annotation, it
* 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.
* <pre>
* private static final class Gadget {
@ -74,8 +70,11 @@ import com.google.gson.TypeAdapter;
* }
* }
* </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
*

View File

@ -38,22 +38,11 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte
this.constructorConstructor = constructorConstructor;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
Class<? super T> clazz = targetType.getRawType();
JsonAdapter annotation = clazz.getAnnotation(JsonAdapter.class);
if (annotation == null) return null;
TypeAdapter adapter = getAnnotationTypeAdapter(gson, constructorConstructor, annotation);
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;
JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class);
return annotation != null
? (TypeAdapter<T>) constructorConstructor.get(TypeToken.get(annotation.value())).construct()
: null;
}
}

View File

@ -16,12 +16,6 @@
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.Gson;
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.JsonToken;
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.
@ -71,9 +70,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
Adapter<T> adapter = new Adapter<T>(constructor, getBoundFields(gson, type, raw));
Gson.$$Internal.addGeneratedTypeAdapter(gson, adapter);
return adapter;
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
private ReflectiveTypeAdapterFactory.BoundField createBoundField(
@ -102,14 +99,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
}
private TypeAdapter<?> getFieldAdapter(Gson gson, Field field, TypeToken<?> fieldType) {
TypeAdapter<?> adapter = gson.getAdapter(fieldType);
boolean generatedAdapter = Gson.$$Internal.isGeneratedTypeAdapter(gson, adapter);
if (generatedAdapter && field.isAnnotationPresent(JsonAdapter.class)) {
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
return JsonAdapterAnnotationTypeAdapterFactory.getAnnotationTypeAdapter(
gson, constructorConstructor, annotation);
}
return adapter;
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
return (annotation != null)
? constructorConstructor.get(TypeToken.get(annotation.value())).construct()
: gson.getAdapter(fieldType);
}
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {

View File

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