diff --git a/jackson-modules/jackson-core/src/main/java/com/baeldung/jackson/staticobjectmapper/JsonUtils.java b/jackson-modules/jackson-core/src/main/java/com/baeldung/jackson/staticobjectmapper/JsonUtils.java new file mode 100644 index 000000000000..ac029223dec7 --- /dev/null +++ b/jackson-modules/jackson-core/src/main/java/com/baeldung/jackson/staticobjectmapper/JsonUtils.java @@ -0,0 +1,7 @@ +package com.baeldung.jackson.staticobjectmapper; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JsonUtils { + public static final ObjectMapper MAPPER = new ObjectMapper(); +} diff --git a/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/ConflictingRequirementsUnitTest.java b/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/ConflictingRequirementsUnitTest.java new file mode 100644 index 000000000000..3530e04fc127 --- /dev/null +++ b/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/ConflictingRequirementsUnitTest.java @@ -0,0 +1,47 @@ +package com.baeldung.jackson.staticobjectmapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import org.junit.jupiter.api.Test; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class ConflictingRequirementsUnitTest { + + private static final ObjectMapper GLOBAL_MAPPER = new ObjectMapper(); + + @Test + void whenSwitchingDateFormatGlobally_thenEndpointsCollide() throws Exception { + SimpleDateFormat iso = new SimpleDateFormat("yyyy-MM-dd"); + GLOBAL_MAPPER.setDateFormat(iso); + + Map payload = Collections.singletonMap( + "dob", + Date.from(LocalDate.of(1990, 10, 5) + .atTime(12, 0) + .toInstant(ZoneOffset.UTC))); + + String forA = GLOBAL_MAPPER.writeValueAsString(payload); + assertEquals("{\"dob\":\"1990-10-05\"}", forA); + + SimpleDateFormat european = new SimpleDateFormat("dd/MM/yyyy"); + GLOBAL_MAPPER.setDateFormat(european); + + String forB = GLOBAL_MAPPER.writeValueAsString(payload); + assertEquals("{\"dob\":\"05/10/1990\"}", forB); + + String nowBrokenForA = GLOBAL_MAPPER.writeValueAsString(payload); + assertNotEquals(forA, nowBrokenForA); + } + +} \ No newline at end of file diff --git a/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/HiddenCouplingUnitTest.java b/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/HiddenCouplingUnitTest.java new file mode 100644 index 000000000000..98dce85a0908 --- /dev/null +++ b/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/HiddenCouplingUnitTest.java @@ -0,0 +1,38 @@ +package com.baeldung.jackson.staticobjectmapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +class HiddenCouplingUnitTest { + + private static final ObjectMapper GLOBAL_MAPPER = new ObjectMapper(); + + @Test + @Order(1) + void givenCustomDateFormat_whenConfiguredFirst_thenPasses() throws Exception { + GLOBAL_MAPPER.setDateFormat(new SimpleDateFormat("dd-MM-yyyy")); + Map payload = Collections.singletonMap("date", Date.from(LocalDate.of(1998, 2, 9).atTime(12, 0).toInstant(ZoneOffset.UTC))); + String json = GLOBAL_MAPPER.writeValueAsString(payload); + assertEquals("{\"date\":\"09-02-1998\"}", json); + } + + @Test + @Order(2) + void givenDefaultDateFormat_whenRunAfterMutation_thenFails() throws Exception { + Map payload = Collections.singletonMap("date", Date.from(LocalDate.of(1998, 2, 9).atTime(12, 0).toInstant(ZoneOffset.UTC))); + String json = GLOBAL_MAPPER.writeValueAsString(payload); + assertNotEquals("{\"date\":887025600000}", json); + } +} \ No newline at end of file diff --git a/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/ObjectMapperThreadSafetyUnitTest.java b/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/ObjectMapperThreadSafetyUnitTest.java new file mode 100644 index 000000000000..532e45a3359f --- /dev/null +++ b/jackson-modules/jackson-core/src/test/java/com/baeldung/jackson/staticobjectmapper/ObjectMapperThreadSafetyUnitTest.java @@ -0,0 +1,79 @@ +package com.baeldung.jackson.staticobjectmapper; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static java.util.Collections.singletonMap; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class ObjectMapperThreadSafetyUnitTest { + + private ObjectMapper GLOBAL_MAPPER = new ObjectMapper(); + + /** + * two real threads, created once and reused for every repetition + */ + private static final ExecutorService POOL = Executors.newFixedThreadPool(2); + + @Test + void whenRegisteringDateFormatGlobally_thenAffectsAllConsumers() throws Exception { + Map payload = singletonMap("today", Date.from(LocalDate.of(1998, 2, 9).atTime(12, 0).toInstant(ZoneOffset.UTC))); + + String before = GLOBAL_MAPPER.writeValueAsString(payload); + assertEquals("{\"today\":887025600000}", before); + + GLOBAL_MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + + String after = GLOBAL_MAPPER.writeValueAsString(payload); + assertEquals("{\"today\":\"1998-02-09\"}", after); + } + + @Test + void whenSimpleDateFormatChanges_thenConflictHappens() throws Exception { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + GLOBAL_MAPPER.setDateFormat(format); + + Callable task = () -> GLOBAL_MAPPER.writeValueAsString(singletonMap("key", Date.from(LocalDate.of(1998, 2, 9).atTime(12, 0).toInstant(ZoneOffset.UTC)))); + Callable mutator = () -> { + format.applyPattern("dd-MM-yyyy"); + return null; + }; + + Future taskResult1 = POOL.submit(task); + assertEquals("{\"key\":\"1998-02-09\"}", taskResult1.get()); + POOL.submit(mutator).get(); + Future taskResult2 = POOL.submit(task); + assertEquals("{\"key\":\"09-02-1998\"}", taskResult2.get()); + } + + @Test + void whenUsingCopyScopedMapper_thenNoInterference() throws Exception { + ObjectMapper localCopy = GLOBAL_MAPPER.copy().enable(SerializationFeature.INDENT_OUTPUT); + assertEquals("{\n \"key\" : \"value\"\n}", localCopy.writeValueAsString(singletonMap("key", "value"))); + assertEquals("{\"key\":\"value\"}", GLOBAL_MAPPER.writeValueAsString(singletonMap("key", "value"))); + } + + @BeforeEach + void setup() { + GLOBAL_MAPPER = new ObjectMapper(); + } + + @AfterAll + static void shutdownPool() { + POOL.shutdownNow(); + } +}