diff --git a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java index 81700e63..35b80305 100644 --- a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java @@ -24,7 +24,6 @@ import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; -import java.util.TimeZone; import com.google.gson.internal.bind.util.ISO8601Utils; diff --git a/gson/src/main/java/com/google/gson/internal/Streams.java b/gson/src/main/java/com/google/gson/internal/Streams.java index 74956d77..ac99910a 100644 --- a/gson/src/main/java/com/google/gson/internal/Streams.java +++ b/gson/src/main/java/com/google/gson/internal/Streams.java @@ -72,7 +72,6 @@ public final class Streams { TypeAdapters.JSON_ELEMENT.write(writer, element); } - @SuppressWarnings("resource") public static Writer writerForAppendable(Appendable appendable) { return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable); } 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 b47aaac4..9d9f6be8 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 @@ -32,6 +32,7 @@ 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; @@ -104,14 +105,22 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { final TypeToken fieldType, boolean serialize, boolean deserialize) { final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); // special casing primitives here saves ~5% on Android... + JsonAdapter annotation = field.getAnnotation(JsonAdapter.class); + TypeAdapter mapped = null; + if (annotation != null) { + mapped = getTypeAdapter(constructorConstructor, context, fieldType, annotation); + } + final boolean jsonAdapterPresent = mapped != null; + if (mapped == null) mapped = context.getAdapter(fieldType); + + final TypeAdapter typeAdapter = mapped; return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) { - final TypeAdapter typeAdapter = getFieldAdapter(context, field, fieldType); @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree @Override void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException { Object fieldValue = field.get(value); - TypeAdapter t = - new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType()); + TypeAdapter t = jsonAdapterPresent ? typeAdapter + : new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType()); t.write(writer, fieldValue); } @Override void read(JsonReader reader, Object value) @@ -129,15 +138,6 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { }; } - TypeAdapter getFieldAdapter(Gson gson, Field field, TypeToken fieldType) { - JsonAdapter annotation = field.getAnnotation(JsonAdapter.class); - 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) { Map result = new LinkedHashMap(); if (raw.isInterface()) { diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java index 7e52c27d..2bf37ad0 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java @@ -15,14 +15,15 @@ */ package com.google.gson.internal.bind; +import java.io.IOException; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + import com.google.gson.Gson; import com.google.gson.TypeAdapter; 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 java.lang.reflect.TypeVariable; final class TypeAdapterRuntimeTypeWrapper extends TypeAdapter { private final Gson context; 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 4c745ec2..6f4a0ce9 100644 --- a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java +++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java @@ -63,7 +63,6 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase { @Override public void write(JsonWriter out, Part part) throws IOException { throw new AssertionError(); } - @Override public Part read(JsonReader in) throws IOException { throw new AssertionError(); } @@ -220,4 +219,53 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase { this.part = part; } } + + /** Regression test contributed through https://github.com/google/gson/issues/831 */ + public void testNonPrimitiveFieldAnnotationTakesPrecedenceOverDefault() { + Gson gson = new Gson(); + String json = gson.toJson(new GadgetWithOptionalPart(new Part("foo"))); + assertEquals("{\"part\":\"PartJsonFieldAnnotationAdapter\"}", json); + GadgetWithOptionalPart gadget = gson.fromJson("{'part':'foo'}", GadgetWithOptionalPart.class); + assertEquals("PartJsonFieldAnnotationAdapter", gadget.part.name); + } + + /** Regression test contributed through https://github.com/google/gson/issues/831 */ + public void testPrimitiveFieldAnnotationTakesPrecedenceOverDefault() { + Gson gson = new Gson(); + String json = gson.toJson(new GadgetWithPrimitivePart(42)); + assertEquals("{\"part\":\"42\"}", json); + GadgetWithPrimitivePart gadget = gson.fromJson(json, GadgetWithPrimitivePart.class); + assertEquals(42, gadget.part); + } + + private static final class GadgetWithPrimitivePart { + @JsonAdapter(LongToStringTypeAdapterFactory.class) + final long part; + + private GadgetWithPrimitivePart(long part) { + this.part = part; + } + } + + private static final class LongToStringTypeAdapterFactory implements TypeAdapterFactory { + static final TypeAdapter ADAPTER = new TypeAdapter() { + @Override public void write(JsonWriter out, Long value) throws IOException { + out.value(value.toString()); + } + @Override public Long read(JsonReader in) throws IOException { + return in.nextLong(); + } + }; + @SuppressWarnings("unchecked") + @Override public TypeAdapter create(Gson gson, final TypeToken type) { + Class cls = type.getRawType(); + if (Long.class.isAssignableFrom(cls)) { + return (TypeAdapter) ADAPTER; + } else if (long.class.isAssignableFrom(cls)) { + return (TypeAdapter) ADAPTER; + } + throw new IllegalStateException("Non-long field of type " + type + + " annotated with @JsonAdapter(LongToStringTypeAdapterFactory.class)"); + } + } }