Skip to content

Commit

Permalink
Don't use a runtime wrapper if a JsonAdapter annotation is present on…
Browse files Browse the repository at this point in the history
… a field.

This ensures that JsonAdapter annotation works correctly on a primitive field.
This is a potentially backward incompatible change.
  • Loading branch information
Inderjeet Singh committed May 17, 2016
1 parent 0f80936 commit 3ff16c3
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import com.google.gson.internal.bind.util.ISO8601Utils;

Expand Down
1 change: 0 additions & 1 deletion gson/src/main/java/com/google/gson/internal/Streams.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public static void write(JsonElement element, JsonWriter writer) throws IOExcept
TypeAdapters.JSON_ELEMENT.write(writer, element);
}

@SuppressWarnings("resource")
public static Writer writerForAppendable(Appendable appendable) {
return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -104,14 +105,22 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
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)
Expand All @@ -129,15 +138,6 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
};
}

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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ public void testFieldAnnotationTakesPrecedenceOverRegisteredTypeAdapter() {
@Override public void write(JsonWriter out, Part part) throws IOException {
throw new AssertionError();
}

@Override public Part read(JsonReader in) throws IOException {
throw new AssertionError();
}
Expand Down Expand Up @@ -220,4 +219,53 @@ private GadgetWithOptionalPart(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)");
}
}
}

0 comments on commit 3ff16c3

Please sign in to comment.