From 5434721c8e83f9b9993453d9ea001a218c859a27 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Wed, 11 Dec 2024 14:40:06 -0600 Subject: [PATCH] Add NullKeyToEmptyStringSerializer - to allow jackson serialization of a map with a null key --- .../qqq/backend/core/utils/JsonUtils.java | 45 +++++++++++++++++++ .../qqq/backend/core/utils/JsonUtilsTest.java | 30 ++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java index 7c000e3e..325e842f 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/utils/JsonUtils.java @@ -30,11 +30,14 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.kingsrook.qqq.backend.core.logging.QLogger; import com.kingsrook.qqq.backend.core.model.data.QRecord; @@ -54,6 +57,11 @@ public class JsonUtils { private static final QLogger LOG = QLogger.getLogger(JsonUtils.class); + ////////////////////////////////////////////////////////////////////// + // see https://www.baeldung.com/jackson-map-null-values-or-null-key // + ////////////////////////////////////////////////////////////////////// + public static NullKeyToEmptyStringSerializer nullKeyToEmptyStringSerializer = new NullKeyToEmptyStringSerializer(); + /******************************************************************************* @@ -396,4 +404,41 @@ public class JsonUtils return (record); } + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class NullKeyToEmptyStringSerializer extends StdSerializer + { + /*************************************************************************** + ** + ***************************************************************************/ + public NullKeyToEmptyStringSerializer() + { + this(null); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public NullKeyToEmptyStringSerializer(Class t) + { + super(t); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void serialize(Object nullKey, JsonGenerator jsonGenerator, SerializerProvider unused) throws IOException + { + jsonGenerator.writeFieldName(""); + } + } + } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java index 8e88739a..bbb4c88a 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/utils/JsonUtilsTest.java @@ -35,11 +35,13 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -81,7 +83,7 @@ class JsonUtilsTest extends BaseTest { objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); }); - + assertThat(json).contains(""" "values":{"foo":"Foo","bar":3.14159,"baz":null}"""); } @@ -318,4 +320,30 @@ class JsonUtilsTest extends BaseTest assertEquals("age", qQueryFilter.getOrderBys().get(0).getFieldName()); assertTrue(qQueryFilter.getOrderBys().get(0).getIsAscending()); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testNullKeyInMap() + { + Map mapWithNullKey = MapBuilder.of(null, "foo"); + + ////////////////////////////////////////////////////// + // assert default behavior throws with null map key // + ////////////////////////////////////////////////////// + assertThatThrownBy(() -> JsonUtils.toJson(mapWithNullKey)).rootCause().hasMessageContaining("Null key for a Map not allowed in JSON"); + + //////////////////////////////////////////////////////////////////////// + // assert that the nullKeyToEmptyStringSerializer does what we expect // + //////////////////////////////////////////////////////////////////////// + assertEquals(""" + {"":"foo"}""", JsonUtils.toJson(mapWithNullKey, mapper -> + { + mapper.getSerializerProvider().setNullKeySerializer(JsonUtils.nullKeyToEmptyStringSerializer); + })); + } + }