diff --git a/utility/synapse-utilities-common/src/main/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfig.java b/utility/synapse-utilities-common/src/main/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfig.java index 2b79a5672..8b11ea477 100644 --- a/utility/synapse-utilities-common/src/main/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfig.java +++ b/utility/synapse-utilities-common/src/main/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfig.java @@ -59,6 +59,12 @@ public class UtilitiesCommonConfig { */ public static final String SYNAPSE_CAMEL_CASE_OBJECT_MAPPER = "synapseCamelCaseObjectMapper"; + /** + * Used to retrieve the ObjectMapper that provides serialization and deserialization + * for camelCase with non-null fields. + */ + public static final String SYNAPSE_SIMPLE_CAMEL_CASE_OBJECT_MAPPER = "synapseSimpleCamelCaseObjectMapper"; + public static final String SYNAPSE_INCLUDE_EMPTY_OBJECT_MAPPER = "synapseIncludeEmptyObjectMapper"; public static final String SYNAPSE_XML_OBJECT_MAPPER = "synapseXmlObjectMapper"; @@ -136,6 +142,21 @@ public ObjectMapper camelCaseObjectMapper() { return mapper; } + /** + * Get the ObjectMapper that provides serialization and deserialization for camelCase, non-null fields and + * fail on unknown properties on deserialization. + * + * @return the ObjectMapper + */ + @Bean(SYNAPSE_SIMPLE_CAMEL_CASE_OBJECT_MAPPER) + public ObjectMapper simpleCamelCaseObjectMapper() { + final ObjectMapper mapper = getInitialObjectMapper(); + mapper.setSerializationInclusion(Include.NON_NULL); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + mapper.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE); + return mapper; + } + @Bean(SYNAPSE_XML_OBJECT_MAPPER) public ObjectMapper xmlObjectMapper() { final ObjectMapper objectMapper = new XmlMapper(); diff --git a/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfigSimpleCamelCaseObjectMapperTest.java b/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfigSimpleCamelCaseObjectMapperTest.java new file mode 100644 index 000000000..ef93dd0a0 --- /dev/null +++ b/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/config/UtilitiesCommonConfigSimpleCamelCaseObjectMapperTest.java @@ -0,0 +1,107 @@ +package io.americanexpress.synapse.utilities.common.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.americanexpress.synapse.utilities.common.io.IOUtils; +import io.americanexpress.synapse.utilities.common.model.SampleDeserializedObject; +import io.americanexpress.synapse.utilities.common.model.SampleNestedCollectionsObject; +import io.americanexpress.synapse.utilities.common.model.SampleNestedObject; +import org.junit.jupiter.api.Test; +import java.time.LocalDate; +import java.util.Collections; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * {@code UtilitiesCommonConfigSimpleCamelCaseObjectMapperTest} tests the + * {@link UtilitiesCommonConfig#simpleCamelCaseObjectMapper} object mapper. + */ +public class UtilitiesCommonConfigSimpleCamelCaseObjectMapperTest { + + private static final String EMPTY_STRING = ""; + private static final String ONE_WHITESPACE_STRING = " "; + private static final ObjectMapper objectMapper = new UtilitiesCommonConfig().simpleCamelCaseObjectMapper(); + + @Test + void readValue_givenEmptyStringsAndCollections_expectedToDeserialize() throws JsonProcessingException { + SampleDeserializedObject actualObject = objectMapper.readValue( + readTestDataJsonAsString("empty-strings-collections-valid.json"), + SampleDeserializedObject.class); + assertThat(actualObject) + .isNotNull() + .extracting( + SampleDeserializedObject::getSomeName, + SampleDeserializedObject::getSomeNumber, + SampleDeserializedObject::getSomeLocalDate) + .containsExactly( + "Stephen Strange", + 123, + LocalDate.of(2018, 4, 1)); + assertThat(actualObject.getSampleNestedObject()) + .isNotNull() + .extracting( + SampleNestedObject::getSomeText1, + SampleNestedObject::getSomeText2) + .containsExactly( + EMPTY_STRING, + ONE_WHITESPACE_STRING); + assertThat(actualObject.getSampleNestedCollectionsObject()).isNotNull(); + assertThat(actualObject.getSampleNestedCollectionsObject().getSomeStringCollection()) + .isEmpty(); + assertThat(actualObject.getSampleNestedCollectionsObject().getSomeStringArray()) + .isEmpty(); + + } + + @Test + void writeValue_givenEmptyStrings_expectedToSerialize() throws JsonProcessingException { + String json = objectMapper.writeValueAsString(sampleDeserializedObjectWithEmptyStringsAndCollections()); + String expectedJson = "{\"someName\":\"Stephen Strange\",\"someNumber\":123," + + "\"sampleNestedObject\":{\"someText1\":\"\",\"someText2\":\" \"}," + + "\"sampleNestedCollectionsObject\":{\"someStringCollection\":[]," + + "\"someStringArray\":[]},\"someLocalDate\":\"2018-04-01\"}"; + assertThat(json).isEqualTo(expectedJson); + } + + @Test + void readValue_givenSnakeCasePropertyNames_expectedToThrowException() { + assertThatThrownBy(() -> objectMapper.readValue( + readTestDataJsonAsString("snakecase-attribute-names-invalid.json"), + SampleDeserializedObject.class)) + .isInstanceOf(JsonProcessingException.class) + .hasMessageContaining("Unrecognized field"); + } + + @Test + void readValue_givenUnknownProperty_expectedToThrowException() { + assertThatThrownBy(() -> objectMapper.readValue( + readTestDataJsonAsString("unknown-property-invalid.json"), + SampleDeserializedObject.class)) + .isInstanceOf(JsonProcessingException.class) + .hasMessageContaining("Unrecognized field \"someUnknownProperty\""); + } + + private SampleDeserializedObject sampleDeserializedObjectWithEmptyStringsAndCollections() { + SampleDeserializedObject sampleDeserializedObject = new SampleDeserializedObject(); + sampleDeserializedObject.setSomeName("Stephen Strange"); + sampleDeserializedObject.setSomeNumber(123); + sampleDeserializedObject.setSomeLocalDate(LocalDate.of(2018, 4, 1)); + + SampleNestedObject sampleNestedObject = new SampleNestedObject(); + sampleNestedObject.setSomeText1(""); + sampleNestedObject.setSomeText2(" "); + sampleDeserializedObject.setSampleNestedObject(sampleNestedObject); + + SampleNestedCollectionsObject sampleNestedCollectionsObject = new SampleNestedCollectionsObject(); + sampleNestedCollectionsObject.setSomeStringCollection(Collections.emptyList()); + sampleNestedCollectionsObject.setSomeStringArray(new String[]{}); + + sampleDeserializedObject.setSampleNestedCollectionsObject(sampleNestedCollectionsObject); + + return sampleDeserializedObject; + } + + private String readTestDataJsonAsString(String fileName) { + return IOUtils.readFileToAString("simple-camel-case-object-mapper-test-data/" + fileName); + } +} diff --git a/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleDeserializedObject.java b/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleDeserializedObject.java index 4c3098f08..a595a28af 100644 --- a/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleDeserializedObject.java +++ b/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleDeserializedObject.java @@ -23,6 +23,8 @@ public class SampleDeserializedObject { private SampleNestedObject sampleNestedObject; + private SampleNestedCollectionsObject sampleNestedCollectionsObject; + private LocalDate someLocalDate; public String getSomeName() { @@ -49,6 +51,14 @@ public void setSampleNestedObject(SampleNestedObject sampleNestedObject) { this.sampleNestedObject = sampleNestedObject; } + public SampleNestedCollectionsObject getSampleNestedCollectionsObject() { + return sampleNestedCollectionsObject; + } + + public void setSampleNestedCollectionsObject(SampleNestedCollectionsObject sampleNestedCollectionsObject) { + this.sampleNestedCollectionsObject = sampleNestedCollectionsObject; + } + public LocalDate getSomeLocalDate() { return someLocalDate; } diff --git a/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleNestedCollectionsObject.java b/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleNestedCollectionsObject.java new file mode 100644 index 000000000..d93f2a6c1 --- /dev/null +++ b/utility/synapse-utilities-common/src/test/java/io/americanexpress/synapse/utilities/common/model/SampleNestedCollectionsObject.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 American Express Travel Related Services Company, 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 io.americanexpress.synapse.utilities.common.model; + +import java.util.Collection; + +public class SampleNestedCollectionsObject { + + private Collection someStringCollection; + + private String[] someStringArray; + + public void setSomeStringCollection(Collection someStringCollection) { + this.someStringCollection = someStringCollection; + } + + public void setSomeStringArray(String[] someStringArray) { + this.someStringArray = someStringArray; + } + + public Collection getSomeStringCollection() { + return someStringCollection; + } + + public String[] getSomeStringArray() { + return someStringArray; + } +} diff --git a/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/empty-strings-collections-valid.json b/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/empty-strings-collections-valid.json new file mode 100644 index 000000000..3ba895af1 --- /dev/null +++ b/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/empty-strings-collections-valid.json @@ -0,0 +1,13 @@ +{ + "someName" : "Stephen Strange", + "someNumber" : 123, + "sampleNestedObject" : { + "someText1" : "", + "someText2" : " " + }, + "sampleNestedCollectionsObject": { + "someStringCollection": [], + "someStringArray": [] + }, + "someLocalDate" : "2018-04-01" +} diff --git a/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/snakecase-attribute-names-invalid.json b/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/snakecase-attribute-names-invalid.json new file mode 100644 index 000000000..d5f3dfbd4 --- /dev/null +++ b/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/snakecase-attribute-names-invalid.json @@ -0,0 +1,9 @@ +{ + "some_name" : "Stephen Strange", + "some_number" : 123, + "sample_nested_object" : { + "some_text1" : "The Eye of Agamotto", + "some_text2" : "The Time Stone" + }, + "some_local_date" : "2018-04-01" +} diff --git a/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/unknown-property-invalid.json b/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/unknown-property-invalid.json new file mode 100644 index 000000000..037dd83b9 --- /dev/null +++ b/utility/synapse-utilities-common/src/test/resources/simple-camel-case-object-mapper-test-data/unknown-property-invalid.json @@ -0,0 +1,10 @@ +{ + "someName" : "Stephen Strange", + "someNumber" : 123, + "sampleNestedObject" : { + "someText1" : "", + "someText2" : " " + }, + "someLocalDate" : "2018-04-01", + "someUnknownProperty" : "This property is not defined in the Java class and should fail to deserialization" +}