Fixed issue 68 by providing support to override default type adapters for

primitive types. Added a visitFieldUsingCustomHandler method to handle this in
the visitors.
This commit is contained in:
Inderjeet Singh 2008-10-31 00:19:58 +00:00
parent 70c7728218
commit cc90a68241
9 changed files with 129 additions and 29 deletions

View File

@ -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),

View File

@ -131,4 +131,8 @@ final class JsonArrayDeserializationVisitor<T> 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();
}
}

View File

@ -38,7 +38,7 @@ abstract class JsonDeserializationVisitor<T> 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,

View File

@ -147,4 +147,24 @@ final class JsonObjectDeserializationVisitor<T> 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();
}
}
}

View File

@ -92,4 +92,9 @@ final class JsonPrimitiveDeserializationVisitor<T> 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();
}
}

View File

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

View File

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

View File

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

View File

@ -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<Long>() {
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 {