Don't use a runtime wrapper if a JsonAdapter annotation is present on a field.
This ensures that JsonAdapter annotation works correctly on a primitive field. This is a potentially backward incompatible change.
This commit is contained in:
parent
0f80936ecd
commit
3ff16c30db
@ -24,7 +24,6 @@ import java.text.ParsePosition;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
import com.google.gson.internal.bind.util.ISO8601Utils;
|
import com.google.gson.internal.bind.util.ISO8601Utils;
|
||||||
|
|
||||||
|
@ -72,7 +72,6 @@ public final class Streams {
|
|||||||
TypeAdapters.JSON_ELEMENT.write(writer, element);
|
TypeAdapters.JSON_ELEMENT.write(writer, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
|
||||||
public static Writer writerForAppendable(Appendable appendable) {
|
public static Writer writerForAppendable(Appendable appendable) {
|
||||||
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
|
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ 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.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
@ -104,14 +105,22 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||||||
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
|
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
|
||||||
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
|
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
|
||||||
// special casing primitives here saves ~5% on Android...
|
// 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) {
|
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
|
@SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
|
||||||
@Override void write(JsonWriter writer, Object value)
|
@Override void write(JsonWriter writer, Object value)
|
||||||
throws IOException, IllegalAccessException {
|
throws IOException, IllegalAccessException {
|
||||||
Object fieldValue = field.get(value);
|
Object fieldValue = field.get(value);
|
||||||
TypeAdapter t =
|
TypeAdapter t = jsonAdapterPresent ? typeAdapter
|
||||||
new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
|
: new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
|
||||||
t.write(writer, fieldValue);
|
t.write(writer, fieldValue);
|
||||||
}
|
}
|
||||||
@Override void read(JsonReader reader, Object value)
|
@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<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
|
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
|
||||||
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
|
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
|
||||||
if (raw.isInterface()) {
|
if (raw.isInterface()) {
|
||||||
|
@ -15,14 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.gson.internal.bind;
|
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.Gson;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
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 java.lang.reflect.Type;
|
|
||||||
import java.lang.reflect.TypeVariable;
|
|
||||||
|
|
||||||
final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
|
final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
|
||||||
private final Gson context;
|
private final Gson context;
|
||||||
|
@ -63,7 +63,6 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
|
|||||||
@Override public void write(JsonWriter out, Part part) throws IOException {
|
@Override public void write(JsonWriter out, Part part) throws IOException {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Part read(JsonReader in) throws IOException {
|
@Override public Part read(JsonReader in) throws IOException {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
@ -220,4 +219,53 @@ public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
|
|||||||
this.part = part;
|
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<Long> ADAPTER = new TypeAdapter<Long>() {
|
||||||
|
@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 <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
|
||||||
|
Class<?> cls = type.getRawType();
|
||||||
|
if (Long.class.isAssignableFrom(cls)) {
|
||||||
|
return (TypeAdapter<T>) ADAPTER;
|
||||||
|
} else if (long.class.isAssignableFrom(cls)) {
|
||||||
|
return (TypeAdapter<T>) ADAPTER;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Non-long field of type " + type
|
||||||
|
+ " annotated with @JsonAdapter(LongToStringTypeAdapterFactory.class)");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user