diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 996f588c..7e93559e 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -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, TypeAdapter> typeTokenCache = Collections.synchronizedMap(new HashMap, 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> preconfiguredGeneratedTypeAdapters = new HashSet>(); - /** List of type adapters that are generated by Gson during toJson/fromJson. */ - private final ThreadLocal>> runtimeGeneratedTypeAdapters = - new ThreadLocal>>(); - private final List 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 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> 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> getRuntimeGeneratedTypeAdapters(Gson gson) { - Set> adapters = gson.runtimeGeneratedTypeAdapters.get(); - if (adapters == null) adapters = new HashSet>(); - gson.runtimeGeneratedTypeAdapters.set(adapters); - return adapters; - } - } } 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 c4d7b302..85525098 100644 --- a/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java +++ b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java @@ -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. * *

Here is an example of how this annotation is used:

*
@@ -61,9 +60,6 @@ import com.google.gson.TypeAdapter;
  * Since User class specified UserJsonAdapter.class in @JsonAdapter annotation, it
  * will automatically be invoked to serialize/deserialize User instances. 
* - * If the UserJsonAdapter needs a constructor other than a no-args constructor, you must register - * an {@link com.google.gson.InstanceCreator} for it. - * *

Here is an example of how to apply this annotation to a field. *

  * private static final class Gadget {
@@ -74,8 +70,11 @@ import com.google.gson.TypeAdapter;
  *   }
  * }
  * 
- * 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 * 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 34f9d389..189da717 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 @@ -38,22 +38,11 @@ public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapte this.constructorConstructor = constructorConstructor; } - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) public TypeAdapter create(Gson gson, TypeToken targetType) { - Class 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> adapterClass = annotation.value(); - ObjectConstructor> 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) constructorConstructor.get(TypeToken.get(annotation.value())).construct() + : null; } } 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 10295fc0..eac09955 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 @@ -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 constructor = constructorConstructor.get(type); - Adapter adapter = new Adapter(constructor, getBoundFields(gson, type, raw)); - Gson.$$Internal.addGeneratedTypeAdapter(gson, adapter); - return adapter; + return new Adapter(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 getBoundFields(Gson context, TypeToken type, Class raw) { 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 c38474b0..fdfaae54 100644 --- a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java +++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java @@ -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() { @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 { + private static class PartJsonFieldAnnotationAdapter extends TypeAdapter { @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 { + private static class UserClassAnnotationAdapter extends TypeAdapter { @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 { + + private static final class UserFieldAnnotationAdapter extends TypeAdapter { @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 { + @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;