Merge pull request #832 from google/831

Ensuring that JsonAdapter annotation works correctly for primitive fi…
This commit is contained in:
Jesse Wilson 2016-05-17 17:26:52 -04:00
commit bb451eac43
5 changed files with 65 additions and 18 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
if (raw.isInterface()) {

View File

@ -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<T> extends TypeAdapter<T> {
private final Gson context;

View File

@ -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<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)");
}
}
}