Added serializeDefault and deserializeDefault methods in contexts that only invoke system type adapters on the top-level object.

With this, the RuntimeTypeAdapterTest passes.
This commit is contained in:
Inderjeet Singh 2011-07-01 21:29:20 +00:00
parent 5f4e88f62a
commit 62675b7f46
15 changed files with 170 additions and 60 deletions

View File

@ -184,7 +184,7 @@ public final class RuntimeTypeAdapter<T> implements JsonSerializer<T>, JsonDeser
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonElement serialized = context.serialize(src, srcType);
JsonElement serialized = context.serializeDefault(src, srcType);
final JsonObject jsonObject = serialized.getAsJsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName()
@ -212,7 +212,7 @@ public final class RuntimeTypeAdapter<T> implements JsonSerializer<T>, JsonDeser
+ label + "; did you forget to register a subtype?");
}
@SuppressWarnings("unchecked") // registration requires that subtype extends T
T result = (T) context.deserialize(json, subtype);
T result = (T) context.deserializeDefault(json, subtype);
return result;
}
}

View File

@ -50,7 +50,7 @@ public final class RuntimeTypeAdapterTest extends TestCase {
assertEquals("{\"type\":\"BillingInstrument\",\"ownerName\":\"Jesse\"}",
gson.toJson(original, BillingInstrument.class));
BillingInstrument deserialized = gson.fromJson(
"{type:'CreditCard',ownerName:'Jesse'}", BillingInstrument.class);
"{type:'BillingInstrument',ownerName:'Jesse'}", BillingInstrument.class);
assertEquals("Jesse", deserialized.ownerName);
}

View File

@ -30,7 +30,7 @@ abstract class BaseMapTypeAdapter
protected static final JsonElement serialize(JsonSerializationContext context,
Object src, Type srcType) {
return context.serialize(src, srcType, false);
return context.serialize(src, srcType, false, false);
}
protected static final Map<Object, Object> constructMapType(

View File

@ -677,7 +677,7 @@ final class DefaultTypeAdapters {
} else {
Type childType = (childGenericType == null || childGenericType == Object.class)
? child.getClass() : childGenericType;
JsonElement element = context.serialize(child, childType, false);
JsonElement element = context.serialize(child, childType, false, false);
array.add(element);
}
}

View File

@ -55,29 +55,29 @@ public final class JsonDeserializationContext {
private <T> T fromJsonArray(Type arrayType, JsonArray jsonArray,
JsonDeserializationContext context) throws JsonParseException {
JsonDeserializationContext context, boolean systemOnly) throws JsonParseException {
JsonArrayDeserializationVisitor<T> visitor = new JsonArrayDeserializationVisitor<T>(
jsonArray, arrayType, objectNavigator, fieldNamingPolicy,
objectConstructor, deserializers, context);
objectNavigator.accept(new ObjectTypePair(null, arrayType, true), visitor);
objectNavigator.accept(new ObjectTypePair(null, arrayType, true, systemOnly), visitor);
return visitor.getTarget();
}
private <T> T fromJsonObject(Type typeOfT, JsonObject jsonObject,
JsonDeserializationContext context) throws JsonParseException {
JsonDeserializationContext context, boolean systemOnly) throws JsonParseException {
JsonObjectDeserializationVisitor<T> visitor = new JsonObjectDeserializationVisitor<T>(
jsonObject, typeOfT, objectNavigator, fieldNamingPolicy,
objectConstructor, deserializers, context);
objectNavigator.accept(new ObjectTypePair(null, typeOfT, true), visitor);
objectNavigator.accept(new ObjectTypePair(null, typeOfT, true, systemOnly), visitor);
return visitor.getTarget();
}
@SuppressWarnings("unchecked")
private <T> T fromJsonPrimitive(Type typeOfT, JsonPrimitive json,
JsonDeserializationContext context) throws JsonParseException {
JsonDeserializationContext context, boolean systemOnly) throws JsonParseException {
JsonObjectDeserializationVisitor<T> visitor = new JsonObjectDeserializationVisitor<T>(
json, typeOfT, objectNavigator, fieldNamingPolicy, objectConstructor, deserializers, context);
objectNavigator.accept(new ObjectTypePair(json.getAsObject(), typeOfT, true), visitor);
objectNavigator.accept(new ObjectTypePair(json.getAsObject(), typeOfT, true, systemOnly), visitor);
Object target = visitor.getTarget();
return (T) target;
}
@ -99,13 +99,31 @@ public final class JsonDeserializationContext {
if (json == null || json.isJsonNull()) {
return null;
} else if (json.isJsonArray()) {
Object array = fromJsonArray(typeOfT, json.getAsJsonArray(), this);
Object array = fromJsonArray(typeOfT, json.getAsJsonArray(), this, false);
return (T) array;
} else if (json.isJsonObject()) {
Object object = fromJsonObject(typeOfT, json.getAsJsonObject(), this);
Object object = fromJsonObject(typeOfT, json.getAsJsonObject(), this, false);
return (T) object;
} else if (json.isJsonPrimitive()) {
Object primitive = fromJsonPrimitive(typeOfT, json.getAsJsonPrimitive(), this);
Object primitive = fromJsonPrimitive(typeOfT, json.getAsJsonPrimitive(), this, false);
return (T) primitive;
} else {
throw new JsonParseException("Failed parsing JSON source: " + json + " to Json");
}
}
@SuppressWarnings("unchecked")
public <T> T deserializeDefault(JsonElement json, Type typeOfT) throws JsonParseException {
if (json == null || json.isJsonNull()) {
return null;
} else if (json.isJsonArray()) {
Object array = fromJsonArray(typeOfT, json.getAsJsonArray(), this, true);
return (T) array;
} else if (json.isJsonObject()) {
Object object = fromJsonObject(typeOfT, json.getAsJsonObject(), this, true);
return (T) object;
} else if (json.isJsonPrimitive()) {
Object primitive = fromJsonPrimitive(typeOfT, json.getAsJsonPrimitive(), this, true);
return (T) primitive;
} else {
throw new JsonParseException("Failed parsing JSON source: " + json + " to Json");

View File

@ -107,7 +107,7 @@ abstract class JsonDeserializationVisitor<T> implements ObjectNavigator.Visitor
}
private Object visitChild(Type type, JsonDeserializationVisitor<?> childVisitor) {
objectNavigator.accept(new ObjectTypePair(null, type, false), childVisitor);
objectNavigator.accept(new ObjectTypePair(null, type, false, false), childVisitor);
// the underlying object may have changed during the construction phase
// This happens primarily because of custom deserializers
return childVisitor.getTarget();

View File

@ -109,7 +109,7 @@ final class JsonObjectDeserializationVisitor<T> extends JsonDeserializationVisit
}
return true;
}
ObjectTypePair objTypePair = new ObjectTypePair(null, declaredTypeOfField, false);
ObjectTypePair objTypePair = new ObjectTypePair(null, declaredTypeOfField, false, false);
Pair<JsonDeserializer<?>, ObjectTypePair> pair = objTypePair.getMatchingHandler(deserializers);
if (pair == null) {
return false;

View File

@ -53,7 +53,7 @@ public final class JsonSerializationContext {
if (src == null) {
return JsonNull.INSTANCE;
}
return serialize(src, src.getClass(), false);
return serialize(src, src.getClass(), false, false);
}
/**
@ -67,16 +67,20 @@ public final class JsonSerializationContext {
* @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}.
*/
public JsonElement serialize(Object src, Type typeOfSrc) {
return serialize(src, typeOfSrc, true);
return serialize(src, typeOfSrc, true, false);
}
JsonElement serialize(Object src, Type typeOfSrc, boolean preserveType) {
public JsonElement serializeDefault(Object src, Type typeOfSrc) {
return serialize(src, typeOfSrc, true, true);
}
JsonElement serialize(Object src, Type typeOfSrc, boolean preserveType, boolean defaultOnly) {
if (src == null) {
return JsonNull.INSTANCE;
}
JsonSerializationVisitor visitor = new JsonSerializationVisitor(
objectNavigator, fieldNamingPolicy, serializeNulls, serializers, this, ancestors);
ObjectTypePair objTypePair = new ObjectTypePair(src, typeOfSrc, preserveType);
ObjectTypePair objTypePair = new ObjectTypePair(src, typeOfSrc, preserveType, defaultOnly);
objectNavigator.accept(objTypePair, visitor);
return visitor.getJsonElement();
}

View File

@ -54,7 +54,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
}
public void start(ObjectTypePair node) {
if (node == null) {
if (node == null || node.isSystemOnly()) {
return;
}
if (ancestors.contains(node)) {
@ -64,7 +64,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
}
public void end(ObjectTypePair node) {
if (node != null) {
if (node != null && !node.isSystemOnly()) {
ancestors.pop();
}
}
@ -81,7 +81,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
Object child = Array.get(array, i);
// we should not get more specific component type yet since it is possible
// that a custom serializer is registered for the componentType
addAsArrayElement(new ObjectTypePair(child, componentType, false));
addAsArrayElement(new ObjectTypePair(child, componentType, false, false));
}
}
@ -93,7 +93,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
}
} else {
Object array = getFieldValue(f, obj);
addAsChildOfObject(f, new ObjectTypePair(array, typeOfF, false));
addAsChildOfObject(f, new ObjectTypePair(array, typeOfF, false, false));
}
} catch (CircularReferenceException e) {
throw e.createDetailedException(f);
@ -111,7 +111,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
// we should not get more specific component type yet since it is
// possible that a custom
// serializer is registered for the componentType
addAsChildOfObject(f, new ObjectTypePair(fieldValue, typeOfF, false));
addAsChildOfObject(f, new ObjectTypePair(fieldValue, typeOfF, false, false));
}
} catch (CircularReferenceException e) {
throw e.createDetailedException(f);
@ -200,7 +200,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor {
}
return true;
}
ObjectTypePair objTypePair = new ObjectTypePair(obj, declaredTypeOfField, false);
ObjectTypePair objTypePair = new ObjectTypePair(obj, declaredTypeOfField, false, false);
JsonElement child = findAndInvokeCustomSerializer(objTypePair);
if (child != null) {
addChildAsElement(f, child);

View File

@ -45,7 +45,7 @@ final class MappedObjectConstructor implements ObjectConstructor {
@SuppressWarnings("unchecked")
public <T> T construct(Type typeOfT) {
InstanceCreator<T> creator = (InstanceCreator<T>) instanceCreatorMap.getHandlerFor(typeOfT);
InstanceCreator<T> creator = (InstanceCreator<T>) instanceCreatorMap.getHandlerFor(typeOfT, false);
if (creator != null) {
return creator.createInstance(typeOfT);
}

View File

@ -27,11 +27,13 @@ final class ObjectTypePair {
private Object obj;
final Type type;
private final boolean preserveType;
private final boolean systemOnly;
ObjectTypePair(Object obj, Type type, boolean preserveType) {
ObjectTypePair(Object obj, Type type, boolean preserveType, boolean systemOnly) {
this.obj = obj;
this.type = type;
this.preserveType = preserveType;
this.systemOnly = systemOnly;
}
Object getObject() {
@ -57,13 +59,13 @@ final class ObjectTypePair {
if (!preserveType && obj != null) {
// First try looking up the handler for the actual type
ObjectTypePair moreSpecificType = toMoreSpecificType();
handler = handlers.getHandlerFor(moreSpecificType.type);
handler = handlers.getHandlerFor(moreSpecificType.type, systemOnly);
if (handler != null) {
return new Pair<HANDLER, ObjectTypePair>(handler, moreSpecificType);
}
}
// Try the specified type
handler = handlers.getHandlerFor(type);
handler = handlers.getHandlerFor(type, systemOnly);
return handler == null ? null : new Pair<HANDLER, ObjectTypePair>(handler, this);
}
@ -75,7 +77,7 @@ final class ObjectTypePair {
if (actualType == type) {
return this;
}
return new ObjectTypePair(obj, actualType, true);
return new ObjectTypePair(obj, actualType, true, systemOnly);
}
Type getMoreSpecificType() {
@ -135,10 +137,14 @@ final class ObjectTypePair {
} else if (!type.equals(other.type)) {
return false;
}
return preserveType == other.preserveType;
return preserveType == other.preserveType && systemOnly == other.systemOnly;
}
public boolean isPreserveType() {
return preserveType;
}
public boolean isSystemOnly() {
return systemOnly;
}
}

View File

@ -164,10 +164,13 @@ final class ParameterizedTypeHandlerMap<T> {
modifiable = false;
}
public synchronized T getHandlerFor(Type type) {
T handler = userMap.get(type);
if (handler != null) {
return handler;
public synchronized T getHandlerFor(Type type, boolean systemOnly) {
T handler;
if (!systemOnly) {
handler = userMap.get(type);
if (handler != null) {
return handler;
}
}
handler = systemMap.get(type);
if (handler != null) {
@ -175,20 +178,22 @@ final class ParameterizedTypeHandlerMap<T> {
}
Class<?> rawClass = $Gson$Types.getRawType(type);
if (rawClass != type) {
handler = getHandlerFor(rawClass);
handler = getHandlerFor(rawClass, systemOnly);
if (handler != null) {
return handler;
}
}
// check if something registered for type hierarchy
handler = getHandlerForTypeHierarchy(rawClass);
handler = getHandlerForTypeHierarchy(rawClass, systemOnly);
return handler;
}
private T getHandlerForTypeHierarchy(Class<?> type) {
for (Pair<Class<?>, T> entry : userTypeHierarchyList) {
if (entry.first.isAssignableFrom(type)) {
return entry.second;
private T getHandlerForTypeHierarchy(Class<?> type, boolean systemOnly) {
if (!systemOnly) {
for (Pair<Class<?>, T> entry : userTypeHierarchyList) {
if (entry.first.isAssignableFrom(type)) {
return entry.second;
}
}
}
for (Pair<Class<?>, T> entry : systemTypeHierarchyList) {

View File

@ -42,7 +42,7 @@ public class MemoryRefStackTest extends TestCase {
}
public void testPushPeekAndPop() throws Exception {
ObjectTypePair obj = new ObjectTypePair(this, getClass(), true);
ObjectTypePair obj = new ObjectTypePair(this, getClass(), true, false);
assertEquals(obj, stack.push(obj));
assertEquals(obj, stack.peek());
@ -50,7 +50,7 @@ public class MemoryRefStackTest extends TestCase {
}
public void testPopTooMany() throws Exception {
ObjectTypePair obj = new ObjectTypePair(this, getClass(), true);
ObjectTypePair obj = new ObjectTypePair(this, getClass(), true, false);
stack.push(obj);
assertEquals(obj, stack.pop());
@ -63,9 +63,9 @@ public class MemoryRefStackTest extends TestCase {
MockObject objA = new MockObject();
MockObject objB = new MockObject();
assertEquals(objA, objB);
stack.push(new ObjectTypePair(objA, MockObject.class, true));
assertTrue(stack.contains(new ObjectTypePair(objA, MockObject.class, true)));
assertFalse(stack.contains(new ObjectTypePair(objB, MockObject.class, true)));
stack.push(new ObjectTypePair(objA, MockObject.class, true, false));
assertTrue(stack.contains(new ObjectTypePair(objA, MockObject.class, true, false)));
assertFalse(stack.contains(new ObjectTypePair(objB, MockObject.class, true, false)));
}
private static class MockObject {

View File

@ -41,8 +41,8 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
public void testNullMap() throws Exception {
assertFalse(paramMap.hasSpecificHandlerFor(String.class));
assertNull(paramMap.getHandlerFor(String.class));
assertNull(paramMap.getHandlerFor(String.class));
assertNull(paramMap.getHandlerFor(String.class, false));
assertNull(paramMap.getHandlerFor(String.class, false));
}
public void testHasGenericButNotSpecific() throws Exception {
@ -52,9 +52,9 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
assertFalse(paramMap.hasSpecificHandlerFor(specificType));
assertTrue(paramMap.hasSpecificHandlerFor(List.class));
assertNotNull(paramMap.getHandlerFor(specificType));
assertNotNull(paramMap.getHandlerFor(List.class));
assertEquals(handler, paramMap.getHandlerFor(specificType));
assertNotNull(paramMap.getHandlerFor(specificType, false));
assertNotNull(paramMap.getHandlerFor(List.class, false));
assertEquals(handler, paramMap.getHandlerFor(specificType, false));
}
public void testHasSpecificType() throws Exception {
@ -64,9 +64,9 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
assertTrue(paramMap.hasSpecificHandlerFor(specificType));
assertFalse(paramMap.hasSpecificHandlerFor(List.class));
assertNotNull(paramMap.getHandlerFor(specificType));
assertNull(paramMap.getHandlerFor(List.class));
assertEquals(handler, paramMap.getHandlerFor(specificType));
assertNotNull(paramMap.getHandlerFor(specificType, false));
assertNull(paramMap.getHandlerFor(List.class, false));
assertEquals(handler, paramMap.getHandlerFor(specificType, false));
}
public void testTypeOverridding() throws Exception {
@ -76,7 +76,7 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
paramMap.register(String.class, handler2, false);
assertTrue(paramMap.hasSpecificHandlerFor(String.class));
assertEquals(handler2, paramMap.getHandlerFor(String.class));
assertEquals(handler2, paramMap.getHandlerFor(String.class, false));
}
public void testMakeUnmodifiable() throws Exception {
@ -89,14 +89,14 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
public void testTypeHierarchy() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler", false);
String handler = paramMap.getHandlerFor(Sub.class);
String handler = paramMap.getHandlerFor(Sub.class, false);
assertEquals("baseHandler", handler);
}
public void testTypeHierarchyMultipleHandlers() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler", false);
paramMap.registerForTypeHierarchy(Sub.class, "subHandler", false);
String handler = paramMap.getHandlerFor(SubOfSub.class);
String handler = paramMap.getHandlerFor(SubOfSub.class, false);
assertEquals("subHandler", handler);
}
@ -105,14 +105,14 @@ public class ParameterizedTypeHandlerMapTest extends TestCase {
ParameterizedTypeHandlerMap<String> otherMap = new ParameterizedTypeHandlerMap<String>();
otherMap.registerForTypeHierarchy(Base.class, "baseHandler2", false);
paramMap.registerIfAbsent(otherMap);
String handler = paramMap.getHandlerFor(Base.class);
String handler = paramMap.getHandlerFor(Base.class, false);
assertEquals("baseHandler", handler);
}
public void testReplaceExistingTypeHierarchyHandler() {
paramMap.registerForTypeHierarchy(Base.class, "baseHandler", false);
paramMap.registerForTypeHierarchy(Base.class, "base2Handler", false);
String handler = paramMap.getHandlerFor(Base.class);
String handler = paramMap.getHandlerFor(Base.class, false);
assertEquals("base2Handler", handler);
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.functional;
import java.lang.reflect.Type;
import junit.framework.TestCase;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
/**
* Functional tests for serialize default behavior where a custom type adapter is allowed to invoke
* context.serialize on self.
*
* @author Inderjeet Singh
*/
public class SystemOnlyTypeAdaptersTest extends TestCase {
private Gson gson;
@Override
protected void setUp() throws Exception {
super.setUp();
this.gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooTypeAdapter()).create();
}
public void testSerializeDefault() {
String json = gson.toJson(new Foo());
assertEquals("{\"a\":10,\"secret-key\":\"abracadabra\"}", json);
}
public void testDeserializeDefault() {
String json = "{a:5,'secret-key':'abracadabra'}";
Foo foo = gson.fromJson(json, Foo.class);
assertEquals(5, foo.a);
}
private static class Foo {
int a = 10;
}
private static class FooTypeAdapter implements JsonSerializer<Foo>, JsonDeserializer<Foo> {
public JsonElement serialize(Foo src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject json = context.serializeDefault(src, typeOfSrc).getAsJsonObject();
json.addProperty("secret-key", "abracadabra");
return json;
}
public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
if (!"abracadabra".equals(json.getAsJsonObject().get("secret-key").getAsString())) {
throw new IllegalArgumentException("invalid key");
}
return context.deserializeDefault(json, typeOfT);
}
}
}