diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index d32362fc..0ee546f7 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -143,9 +143,6 @@ public final class Gson { /** * Constructs a Gson object with the specified version and the mode of operation while * encountering inner class references. - * - * @param factory the object navigator factory to use when creating a new {@link ObjectNavigator} - * instance */ Gson(ExclusionStrategy strategy, FieldNamingStrategy fieldNamingPolicy) { this(strategy, fieldNamingPolicy, createObjectConstructor(DefaultTypeAdapters.DEFAULT_INSTANCE_CREATORS), diff --git a/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java index 67c2d544..affc6da7 100644 --- a/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java @@ -131,4 +131,8 @@ final class JsonArrayDeserializationVisitor extends JsonDeserializationVisito public void visitPrimitiveField(Field f, Type typeOfF, Object obj) { throw new UnsupportedOperationException(); } + + public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent) { + throw new UnsupportedOperationException(); + } } diff --git a/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java index 8d7cef00..49648965 100644 --- a/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonDeserializationVisitor.java @@ -38,7 +38,7 @@ abstract class JsonDeserializationVisitor implements ObjectNavigator.Visitor protected T target; protected final JsonElement json; protected final Type targetType; - private final JsonDeserializationContext context; + protected final JsonDeserializationContext context; public JsonDeserializationVisitor(JsonElement json, Type targetType, ObjectNavigatorFactory factory, ObjectConstructor objectConstructor, TypeAdapter typeAdapter, diff --git a/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java index bbf19db3..8c8c180f 100644 --- a/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java @@ -147,4 +147,24 @@ final class JsonObjectDeserializationVisitor extends JsonDeserializationVisit FieldNamingStrategy namingPolicy = factory.getFieldNamingPolicy(); return namingPolicy.translateName(f); } + + public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent) { + try { + @SuppressWarnings("unchecked") + JsonDeserializer deserializer = deserializers.getHandlerFor(actualTypeOfField); + if (deserializer != null) { + String fName = getFieldName(f); + JsonElement child = json.getAsJsonObject().get(fName); + if (child == null) { + child = JsonNull.INSTANCE; + } + Object value = deserializer.deserialize(child, actualTypeOfField, context); + f.set(parent, value); + return true; + } + return false; + } catch (IllegalAccessException e) { + throw new RuntimeException(); + } + } } diff --git a/gson/src/main/java/com/google/gson/JsonPrimitiveDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonPrimitiveDeserializationVisitor.java index 011774f6..5b225ab5 100644 --- a/gson/src/main/java/com/google/gson/JsonPrimitiveDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonPrimitiveDeserializationVisitor.java @@ -92,4 +92,9 @@ final class JsonPrimitiveDeserializationVisitor extends JsonDeserializationVi // should not be called since this case should invoke JsonArrayDeserializationVisitor throw new IllegalStateException(); } + + public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent) { + // should not be called since this case should invoke JsonObjectDeserializationVisitor + throw new IllegalStateException(); + } } diff --git a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java index 3a5fa043..578e8a42 100644 --- a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java @@ -191,12 +191,36 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { serializer = serializers.getHandlerFor(Map.class); } if (serializer != null) { - assignToRoot(serializer.serialize(obj, objType, context)); + if (obj == null) { + assignToRoot(JsonNull.INSTANCE); + } else { + assignToRoot(serializer.serialize(obj, objType, context)); + } return true; } return false; } + @SuppressWarnings("unchecked") + public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent) { + try { + Preconditions.checkState(root.isJsonObject()); + Object obj = f.get(parent); + JsonSerializer serializer = serializers.getHandlerFor(actualTypeOfField); + if (serializer == null && obj instanceof Map) { + serializer = serializers.getHandlerFor(Map.class); + } + if (serializer != null) { + JsonElement child = serializer.serialize(obj, actualTypeOfField, context); + addChildAsElement(f, child); + return true; + } + return false; + } catch (IllegalAccessException e) { + throw new RuntimeException(); + } + } + private void assignToRoot(JsonElement newRoot) { Preconditions.checkArgument(root == null); root = newRoot; diff --git a/gson/src/main/java/com/google/gson/ObjectNavigator.java b/gson/src/main/java/com/google/gson/ObjectNavigator.java index 79eeaaec..cb990268 100644 --- a/gson/src/main/java/com/google/gson/ObjectNavigator.java +++ b/gson/src/main/java/com/google/gson/ObjectNavigator.java @@ -87,6 +87,11 @@ final class ObjectNavigator { * @return true if a custom handler exists, false otherwise */ public boolean visitUsingCustomHandler(Object obj, Type objType); + + /** + * This is called to visit a field of the current object using a custom handler + */ + public boolean visitFieldUsingCustomHandler(Field f, Type actualTypeOfField, Object parent); } private final ExclusionStrategy exclusionStrategy; @@ -130,23 +135,24 @@ final class ObjectNavigator { ancestors.push(obj); try { - if (objTypeInfo.isCollectionOrArray()) { - if (objTypeInfo.isArray()) { - visitor.visitArray(obj, objType); - } else { // must be a collection - visitor.visitCollection((Collection) obj, objType); - } - } else if (objTypeInfo.isEnum()) { - visitor.visitEnum(obj, objType); - } else if (objTypeInfo.isPrimitiveOrStringAndNotAnArray()) { - visitor.visitPrimitiveValue(obj); - } else { - if (!visitor.visitUsingCustomHandler(obj, objType)) { + boolean visitedWithCustomHandler = visitor.visitUsingCustomHandler(obj, objType); + if (!visitedWithCustomHandler) { + if (objTypeInfo.isCollectionOrArray()) { + if (objTypeInfo.isArray()) { + visitor.visitArray(obj, objType); + } else { // must be a collection + visitor.visitCollection((Collection) obj, objType); + } + } else if (objTypeInfo.isEnum()) { + visitor.visitEnum(obj, objType); + } else if (objTypeInfo.isPrimitiveOrStringAndNotAnArray()) { + visitor.visitPrimitiveValue(obj); + } else { visitor.startVisitingObject(obj); // For all classes in the inheritance hierarchy (including the current class), // visit all fields for (Class curr = objTypeInfo.getRawClass(); - curr != null && !curr.equals(Object.class); curr = curr.getSuperclass()) { + curr != null && !curr.equals(Object.class); curr = curr.getSuperclass()) { if (!curr.isSynthetic()) { navigateClassFields(obj, curr, visitor); } @@ -167,16 +173,22 @@ final class ObjectNavigator { Type actualTypeOfField = fieldTypeInfo.getActualType(); if (exclusionStrategy.shouldSkipField(f)) { continue; // skip - } else if (fieldTypeInfo.isCollectionOrArray()) { - if (fieldTypeInfo.isArray()) { - visitor.visitArrayField(f, actualTypeOfField, obj); - } else { // must be Collection - visitor.visitCollectionField(f, actualTypeOfField, obj); - } - } else if (fieldTypeInfo.isPrimitiveOrStringAndNotAnArray()) { - visitor.visitPrimitiveField(f, actualTypeOfField, obj); } else { - visitor.visitObjectField(f, actualTypeOfField, obj); + boolean visitedWithCustomHandler = + visitor.visitFieldUsingCustomHandler(f, actualTypeOfField, obj); + if (!visitedWithCustomHandler) { + if (fieldTypeInfo.isCollectionOrArray()) { + if (fieldTypeInfo.isArray()) { + visitor.visitArrayField(f, actualTypeOfField, obj); + } else { // must be Collection + visitor.visitCollectionField(f, actualTypeOfField, obj); + } + } else if (fieldTypeInfo.isPrimitiveOrStringAndNotAnArray()) { + visitor.visitPrimitiveField(f, actualTypeOfField, obj); + } else { + visitor.visitObjectField(f, actualTypeOfField, obj); + } + } } } } diff --git a/gson/src/main/java/com/google/gson/Preconditions.java b/gson/src/main/java/com/google/gson/Preconditions.java index 72423278..a37477f7 100644 --- a/gson/src/main/java/com/google/gson/Preconditions.java +++ b/gson/src/main/java/com/google/gson/Preconditions.java @@ -39,4 +39,10 @@ final class Preconditions { throw new IllegalArgumentException("condition failed: " + condition); } } + + public static void checkState(boolean condition) { + if (!condition) { + throw new IllegalArgumentException("condition failed: " + condition); + } + } } diff --git a/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java index f600a49f..d77a4ec9 100644 --- a/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java @@ -20,6 +20,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; +import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; @@ -196,6 +197,27 @@ public class CustomTypeAdaptersTest extends TestCase { } } + public void testCustomSerializerForLong() { + final ClassWithBooleanField customSerializerInvoked = new ClassWithBooleanField(); + customSerializerInvoked.value = false; + Gson gson = new GsonBuilder().registerTypeAdapter(Long.class, new JsonSerializer() { + public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) { + customSerializerInvoked.value = true; + return src == null ? new JsonNull() : new JsonPrimitive(src); + } + }).serializeNulls().create(); + ClassWithWrapperLongField src = new ClassWithWrapperLongField(); + String json = gson.toJson(src); + assertTrue(json.contains("\"value\":null")); + assertTrue(customSerializerInvoked.value); + + customSerializerInvoked.value = false; + src.value = 10L; + json = gson.toJson(src); + assertTrue(json.contains("\"value\":10")); + assertTrue(customSerializerInvoked.value); + } + public void testCustomDeserializerForLong() { final ClassWithBooleanField customDeserializerInvoked = new ClassWithBooleanField(); customDeserializerInvoked.value = false; @@ -203,14 +225,24 @@ public class CustomTypeAdaptersTest extends TestCase { public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { customDeserializerInvoked.value = true; - String str = json.getAsJsonPrimitive().getAsString(); - return str.length() == 0 ? null : Long.parseLong(str); + if (json == null || json.isJsonNull()) { + return null; + } else { + Number number = json.getAsJsonPrimitive().getAsNumber(); + return number == null ? null : number.longValue(); + } } }).create(); String json = "{'value':null}"; ClassWithWrapperLongField target = gson.fromJson(json, ClassWithWrapperLongField.class); assertNull(target.value); assertTrue(customDeserializerInvoked.value); + + customDeserializerInvoked.value = false; + json = "{'value':10}"; + target = gson.fromJson(json, ClassWithWrapperLongField.class); + assertEquals(10L, target.value.longValue()); + assertTrue(customDeserializerInvoked.value); } private static class ClassWithWrapperLongField {