org.slf4j
slf4j-api
diff --git a/src/main/java/com/retailsvc/http/JacksonJsonTypeMapper.java b/src/main/java/com/retailsvc/http/Jackson2JsonTypeMapper.java
similarity index 88%
rename from src/main/java/com/retailsvc/http/JacksonJsonTypeMapper.java
rename to src/main/java/com/retailsvc/http/Jackson2JsonTypeMapper.java
index dd95abb..f1184ef 100644
--- a/src/main/java/com/retailsvc/http/JacksonJsonTypeMapper.java
+++ b/src/main/java/com/retailsvc/http/Jackson2JsonTypeMapper.java
@@ -18,7 +18,7 @@
* {@code
* OpenApiServer.builder()
* .spec(spec)
- * .bodyMapper("application/json", new JacksonJsonTypeMapper(myObjectMapper))
+ * .bodyMapper("application/json", new Jackson2JsonTypeMapper(myObjectMapper))
* .handlers(handlers)
* .build();
* }
@@ -27,11 +27,11 @@
* must declare {@code jackson-databind} themselves. Consumers that use Gson can rely on the
* built-in {@code GsonJsonMapper} auto-fallback instead.
*/
-public final class JacksonJsonTypeMapper implements TypedTypeMapper {
+public final class Jackson2JsonTypeMapper implements TypedTypeMapper {
private final ObjectMapper mapper;
- public JacksonJsonTypeMapper(ObjectMapper mapper) {
+ public Jackson2JsonTypeMapper(ObjectMapper mapper) {
this.mapper = Objects.requireNonNull(mapper, "mapper must not be null");
}
diff --git a/src/main/java/com/retailsvc/http/Jackson3JsonTypeMapper.java b/src/main/java/com/retailsvc/http/Jackson3JsonTypeMapper.java
new file mode 100644
index 0000000..57f232b
--- /dev/null
+++ b/src/main/java/com/retailsvc/http/Jackson3JsonTypeMapper.java
@@ -0,0 +1,58 @@
+package com.retailsvc.http;
+
+import java.util.Objects;
+import tools.jackson.databind.ObjectMapper;
+
+/**
+ * {@link TypeMapper} for {@code application/json} backed by Jackson 3. The caller supplies a
+ * fully-configured {@link ObjectMapper}; this class never adds modules or changes settings — the
+ * mapper you pass is the mapper you get.
+ *
+ * Implements {@link TypedTypeMapper}, so handlers can ask for a typed view of the body via
+ * {@link Request#asPojo(Class)}.
+ *
+ *
Use this adapter for Jackson 3.x (group {@code tools.jackson.core}). For Jackson 2.x (group
+ * {@code com.fasterxml.jackson.core}), use {@link Jackson2JsonTypeMapper} instead — the two majors
+ * use disjoint package roots and can coexist on the same classpath.
+ *
+ *
Jackson is an optional Maven dependency of this library; consumers that use Jackson
+ * must declare {@code tools.jackson.core:jackson-databind} themselves. Consumers that use Gson can
+ * rely on the built-in {@code GsonJsonMapper} auto-fallback instead.
+ *
+ *
Typical wiring:
+ *
+ *
{@code
+ * OpenApiServer.builder()
+ * .spec(spec)
+ * .bodyMapper("application/json", new Jackson3JsonTypeMapper(myObjectMapper))
+ * .handlers(handlers)
+ * .build();
+ * }
+ *
+ * Jackson 3 made all I/O exceptions unchecked ({@code tools.jackson.core.JacksonException
+ * extends RuntimeException}), so this adapter no longer needs to wrap them — read/write failures
+ * propagate as-is to the caller.
+ */
+public final class Jackson3JsonTypeMapper implements TypedTypeMapper {
+
+ private final ObjectMapper mapper;
+
+ public Jackson3JsonTypeMapper(ObjectMapper mapper) {
+ this.mapper = Objects.requireNonNull(mapper, "mapper must not be null");
+ }
+
+ @Override
+ public Object readFrom(byte[] body, String contentTypeHeader) {
+ return readAs(body, contentTypeHeader, Object.class);
+ }
+
+ @Override
+ public T readAs(byte[] body, String contentTypeHeader, Class type) {
+ return mapper.readValue(body, type);
+ }
+
+ @Override
+ public byte[] writeTo(Object value) {
+ return mapper.writeValueAsBytes(value);
+ }
+}
diff --git a/src/test/java/com/retailsvc/http/JacksonJsonTypeMapperTest.java b/src/test/java/com/retailsvc/http/Jackson2JsonTypeMapperTest.java
similarity index 87%
rename from src/test/java/com/retailsvc/http/JacksonJsonTypeMapperTest.java
rename to src/test/java/com/retailsvc/http/Jackson2JsonTypeMapperTest.java
index 1c2ae0f..6501b7a 100644
--- a/src/test/java/com/retailsvc/http/JacksonJsonTypeMapperTest.java
+++ b/src/test/java/com/retailsvc/http/Jackson2JsonTypeMapperTest.java
@@ -10,9 +10,9 @@
import java.util.Map;
import org.junit.jupiter.api.Test;
-class JacksonJsonTypeMapperTest {
+class Jackson2JsonTypeMapperTest {
- private final JacksonJsonTypeMapper mapper = new JacksonJsonTypeMapper(new ObjectMapper());
+ private final Jackson2JsonTypeMapper mapper = new Jackson2JsonTypeMapper(new ObjectMapper());
@Test
void readsJsonObjectAsMap() {
@@ -43,7 +43,7 @@ void wrapsReadFailureAsUncheckedIOException() {
@Test
void rejectsNullObjectMapper() {
- assertThatThrownBy(() -> new JacksonJsonTypeMapper(null))
+ assertThatThrownBy(() -> new Jackson2JsonTypeMapper(null))
.isInstanceOf(NullPointerException.class)
.hasMessageContaining("mapper");
}
diff --git a/src/test/java/com/retailsvc/http/Jackson3JsonTypeMapperTest.java b/src/test/java/com/retailsvc/http/Jackson3JsonTypeMapperTest.java
new file mode 100644
index 0000000..6cc97d0
--- /dev/null
+++ b/src/test/java/com/retailsvc/http/Jackson3JsonTypeMapperTest.java
@@ -0,0 +1,50 @@
+package com.retailsvc.http;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.ObjectMapper;
+
+class Jackson3JsonTypeMapperTest {
+
+ private final Jackson3JsonTypeMapper mapper = new Jackson3JsonTypeMapper(new ObjectMapper());
+
+ @Test
+ void readsJsonObjectAsMap() {
+ byte[] body = "{\"n\":42,\"s\":\"hi\",\"a\":[1,2]}".getBytes(StandardCharsets.UTF_8);
+
+ Object parsed = mapper.readFrom(body, "application/json");
+
+ assertThat(parsed).isInstanceOf(Map.class);
+ @SuppressWarnings("unchecked")
+ Map m = (Map) parsed;
+ assertThat(m).containsEntry("n", 42).containsEntry("s", "hi").containsEntry("a", List.of(1, 2));
+ }
+
+ @Test
+ void writesMapAsJson() {
+ byte[] out = mapper.writeTo(Map.of("k", "v"));
+
+ assertThat(new String(out, StandardCharsets.UTF_8)).isEqualTo("{\"k\":\"v\"}");
+ }
+
+ @Test
+ void readFailurePropagatesAsJacksonException() {
+ byte[] malformed = "not json".getBytes(StandardCharsets.UTF_8);
+
+ assertThatThrownBy(() -> mapper.readFrom(malformed, "application/json"))
+ .isInstanceOf(JacksonException.class);
+ }
+
+ @Test
+ void rejectsNullObjectMapper() {
+ assertThatThrownBy(() -> new Jackson3JsonTypeMapper(null))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessageContaining("mapper");
+ }
+}
diff --git a/src/test/java/com/retailsvc/http/RequestTest.java b/src/test/java/com/retailsvc/http/RequestTest.java
index 98afcb5..5eef1aa 100644
--- a/src/test/java/com/retailsvc/http/RequestTest.java
+++ b/src/test/java/com/retailsvc/http/RequestTest.java
@@ -59,7 +59,7 @@ void readsBoundContext() throws Exception {
@Test
void asPojoDeserialisesViaTypedMapper() {
- JacksonJsonTypeMapper mapper = new JacksonJsonTypeMapper(new ObjectMapper());
+ Jackson2JsonTypeMapper mapper = new Jackson2JsonTypeMapper(new ObjectMapper());
byte[] body = "{\"id\":\"x-1\",\"qty\":7}".getBytes(StandardCharsets.UTF_8);
Request req =
new Request(