From bb5818d982851a9fba6bcc4f71dc717d3b807964 Mon Sep 17 00:00:00 2001 From: Phil Adams Date: Mon, 13 Jul 2020 11:34:19 -0500 Subject: [PATCH] fix: explicitly serialize null values found in dynamic properties --- .../util/DynamicModelTypeAdapterFactory.java | 19 +++++++--- .../model/DynamicModelSerializationTest.java | 35 +++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/ibm/cloud/sdk/core/util/DynamicModelTypeAdapterFactory.java b/src/main/java/com/ibm/cloud/sdk/core/util/DynamicModelTypeAdapterFactory.java index dac3b0cc7..c13b211ac 100644 --- a/src/main/java/com/ibm/cloud/sdk/core/util/DynamicModelTypeAdapterFactory.java +++ b/src/main/java/com/ibm/cloud/sdk/core/util/DynamicModelTypeAdapterFactory.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corp. 2019. + * (C) Copyright IBM Corp. 2019, 2020. * 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 @@ -67,6 +67,9 @@ * *

This class includes code that was adapted from the internal ReflectiveTypeAdapterFactory and * MapTypeAdapterFactory classes from Gson. + * + *

This class will explicitly serialize null values found within dynamic (additional) properties, regardless of + * the global "serialize nulls" setting in Gson. */ public class DynamicModelTypeAdapterFactory implements TypeAdapterFactory { private static final Logger LOGGER = Logger.getLogger(DynamicModelTypeAdapterFactory.class.getName()); @@ -331,9 +334,17 @@ public void write(JsonWriter out, T value) throws IOException { } // Next, serialize each of the map entries. - for (String key : ((DynamicModel) value).getPropertyNames()) { - out.name(String.valueOf(key)); - mapValueTypeAdapter.write(out, ((DynamicModel) value).get(key)); + // When serializing the map entries (i.e. additional/dynamic properties) we want + // to explicitly serialize null values regardless of the global Gson "serialize nulls" setting. + boolean serializeNulls = out.getSerializeNulls(); + out.setSerializeNulls(true); + try { + for (String key : ((DynamicModel) value).getPropertyNames()) { + out.name(String.valueOf(key)); + mapValueTypeAdapter.write(out, ((DynamicModel) value).get(key)); + } + } finally { + out.setSerializeNulls(serializeNulls); } } catch (IllegalAccessException e) { throw new AssertionError(e); diff --git a/src/test/java/com/ibm/cloud/sdk/core/test/model/DynamicModelSerializationTest.java b/src/test/java/com/ibm/cloud/sdk/core/test/model/DynamicModelSerializationTest.java index ff6c21c8f..953baa7f4 100644 --- a/src/test/java/com/ibm/cloud/sdk/core/test/model/DynamicModelSerializationTest.java +++ b/src/test/java/com/ibm/cloud/sdk/core/test/model/DynamicModelSerializationTest.java @@ -1,5 +1,5 @@ /** - * (C) Copyright IBM Corp. 2015, 2019. + * (C) Copyright IBM Corp. 2015, 2020. * * 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 @@ -54,15 +54,18 @@ private T deserialize(String json, Class clazz) { return GsonSingleton.getGson().fromJson(json, clazz); } - private void testSerDeser(DynamicModel model, Class clazz) { - String jsonString = serialize(model); + private void display(String msg) { if (displayOutput) { - System.out.println("serialized " + model.getClass().getSimpleName() + ": " + jsonString); + System.out.println(msg); } + } + + private void testSerDeser(DynamicModel model, Class clazz) { + String jsonString = serialize(model); + + display("serialized " + model.getClass().getSimpleName() + ": " + jsonString); T newModel = deserialize(jsonString, clazz); - if (displayOutput) { - System.out.println("de-serialized " + model.getClass().getSimpleName() + ": " + newModel.toString()); - } + display("de-serialized " + model.getClass().getSimpleName() + ": " + newModel.toString()); assertEquals(newModel, model); } @@ -203,19 +206,31 @@ public void testNoCtor() { public void testNullValues() { ModelAPFoo model = createModelAPFoo(); model.setProp1(null); - // model.put("basketball", "foo"); testSerDeser(model, ModelAPFoo.class); } + @Test + public void testAddlPropsNull() { + ModelAPString model = createModelAPString(); + model.put("basketball", null); + + String json = serialize(model); + display("Serialized: " + json); + assertTrue(json.contains("\"basketball\": null")); + + ModelAPString newModel = deserialize(json, ModelAPString.class); + assertEquals(newModel, model); + } + @Test(expectedExceptions = {JsonSyntaxException.class}) public void testBadDeser() { // Obtain the json string and then render it incorrect to trigger a deserialization error. ModelAPFoo model = createModelAPFoo(); String goodJson = serialize(model); - if (displayOutput) System.out.println("Serialized ModelAPFoo: " + goodJson); + display("Serialized ModelAPFoo: " + goodJson); String badJson = goodJson.replaceAll("foo", "FOO").replaceAll("prop2", "var2"); - if (displayOutput) System.out.println("Incorrect JSON: " + badJson); + display("Incorrect JSON: " + badJson); // We just need to try to deserialize the bad JSON string to trigger the exception. deserialize(badJson, ModelAPFoo.class);