openstack: adjusting deserialization to treat {} as null if (and ONLY if) a null pointer is thrown when attempting to construct an object from the empty json object ({})

This commit is contained in:
Adam Lowe 2012-07-02 18:15:38 +01:00
parent 908e164698
commit 3243325878
2 changed files with 59 additions and 4 deletions

View File

@ -52,6 +52,9 @@ import com.google.gson.stream.JsonWriter;
* If there's an annotation designating a parameterized constructor, invoke that for fields
* correlating to named parameter annotations. Otherwise, use {@link ConstructorConstructor}, and
* set fields via reflection.
* <p/>
* Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and
* the empty object ({}) is treated as a null if the constructor for the object throws an NPE.
* <li>Serialization</li>
* Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor.
* </ul>
@ -155,8 +158,9 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
Class<?>[] paramTypes = parameterizedCtor.getParameterTypes();
Object[] ctorParams = new Object[paramTypes.length];
boolean empty = true;
// TODO determine if we can drop this
// Set all primitive constructor params to defaults
for (int i = 0; i < paramTypes.length; i++) {
if (paramTypes[i] == boolean.class) {
ctorParams[i] = Boolean.FALSE;
@ -168,6 +172,7 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
try {
in.beginObject();
while (in.hasNext()) {
empty = false;
String name = in.nextName();
ParameterReader parameter = parameterReaders.get(name);
if (parameter == null) {
@ -183,12 +188,21 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
for (int i = 0; i < paramTypes.length; i++) {
if (paramTypes[i].isPrimitive()) {
checkArgument(ctorParams[i] != null, "Primative param[" + i + "] in constructor " + parameterizedCtor
checkArgument(ctorParams[i] != null, "Primitive param[" + i + "] in constructor " + parameterizedCtor
+ " cannot be absent!");
}
}
in.endObject();
try {
return newInstance(ctorParams);
} catch (NullPointerException ex) {
// If {} was found and constructor threw NPE, we treat the field as null
if (empty && paramTypes.length > 0) {
return null;
}
throw ex;
}
}
/**

View File

@ -18,6 +18,7 @@ package org.jclouds.json.internal;
* under the License.
*/
import static com.google.common.base.Preconditions.checkNotNull;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotSame;
import static org.testng.Assert.assertNull;
@ -38,6 +39,7 @@ import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
import org.testng.annotations.Test;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
@ -164,6 +166,8 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
return true;
}
@Override
public String toString() { return "ValidatedConstructor[foo=" + foo + ",bar=" + bar + "]"; }
}
public void testValidatedConstructor() throws IOException {
@ -227,7 +231,6 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
return false;
return true;
}
}
public void testRenamedFields() throws IOException {
@ -236,6 +239,44 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
assertEquals(adapter.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}");
}
private static class ComposedObjects {
final ValidatedConstructor x;
final ValidatedConstructor y;
@ConstructorProperties({"x", "y"})
ComposedObjects(ValidatedConstructor x, ValidatedConstructor y) {
this.x = checkNotNull(x);
this.y = checkNotNull(y);
}
@Override
public boolean equals(Object obj) {
ComposedObjects other = ComposedObjects.class.cast(obj);
return other != null && Objects.equal(x, other.x) && Objects.equal(y, other.y);
}
@Override
public String toString() { return "ComposedObjects[x=" + x.toString() + ";y=" + y.toString() + "]"; }
}
public void checkSimpleComposedObject() throws IOException {
ValidatedConstructor x = new ValidatedConstructor(0,1);
ValidatedConstructor y = new ValidatedConstructor(1,2);
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class));
assertEquals(new ComposedObjects(x, y), adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}"));
}
public void testEmptyObjectIsNull() throws IOException {
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class));
assertNull(adapter.fromJson("{}"));
}
@Test(expectedExceptions = NullPointerException.class)
public void testPartialObjectStillThrows() throws IOException {
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class));
assertNull(adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1}}"));
}
public void testCanOverrideDefault() throws IOException {
Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory).create();