From 3243325878a97ff3ccdd187ec301050d63b0f887 Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Mon, 2 Jul 2012 18:15:38 +0100 Subject: [PATCH] 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 ({}) --- ...ructorAndReflectiveTypeAdapterFactory.java | 20 +++++++-- ...orAndReflectiveTypeAdapterFactoryTest.java | 43 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java index 14ce076ce0..4da8913ecd 100644 --- a/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java +++ b/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java @@ -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. + *

+ * 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. *

  • Serialization
  • * Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor. * @@ -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(); - return newInstance(ctorParams); + + 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; + } } /** diff --git a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java index 995474fe0f..478a3f1b17 100644 --- a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java +++ b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java @@ -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 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 adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class)); + assertNull(adapter.fromJson("{}")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testPartialObjectStillThrows() throws IOException { + TypeAdapter 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();