From 0cc985ec08761c3c639666a9e19c8d133f82344d Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 5 Dec 2024 16:24:35 +0200 Subject: [PATCH] Use the Quarkus configured ObjectMapper in Redis client Fixes: #44934 --- .../datasource/QuarkusObjectMapperTest.java | 134 ++++++++++++++++++ .../redis/datasource/codecs/Codecs.java | 4 +- .../jackson/QuarkusJacksonJsonCodec.java | 4 +- 3 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/QuarkusObjectMapperTest.java diff --git a/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/QuarkusObjectMapperTest.java b/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/QuarkusObjectMapperTest.java new file mode 100644 index 0000000000000..a13ded00f6789 --- /dev/null +++ b/extensions/redis-client/deployment/src/test/java/io/quarkus/redis/deployment/client/datasource/QuarkusObjectMapperTest.java @@ -0,0 +1,134 @@ +package io.quarkus.redis.deployment.client.datasource; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import io.quarkus.jackson.ObjectMapperCustomizer; +import io.quarkus.redis.datasource.RedisDataSource; +import io.quarkus.redis.datasource.hash.HashCommands; +import io.quarkus.redis.deployment.client.RedisTestResource; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.QuarkusTestResource; + +@QuarkusTestResource(RedisTestResource.class) +public class QuarkusObjectMapperTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class).addClass(CustomCodecTest.Jedi.class).addClass( + CustomCodecTest.Sith.class) + .addClass(CustomCodecTest.CustomJediCodec.class).addClass(CustomCodecTest.CustomSithCodec.class)) + .overrideConfigKey("quarkus.redis.hosts", "${quarkus.redis.tr}"); + + @Inject + RedisDataSource ds; + + @Test + public void test() { + String key = UUID.randomUUID().toString(); + HashCommands> h = ds.hash(new TypeReference<>() { + + }); + h.hset(key, "test", List.of(new Person("foo", 100))); + String stringRetrieved = ds.hash(String.class).hget(key, "test"); + assertThat(stringRetrieved).isEqualTo("[{\"nAmE\":\"foo\",\"aGe\":100}]"); + List peopleRetrieved = h.hget(key, "test"); + assertThat(peopleRetrieved).singleElement().satisfies(p -> { + assertThat(p.getName()).isEqualTo("foo"); + assertThat(p.getAge()).isEqualTo(100); + }); + } + + // without a custom module, this could not be deserialized as there are 2 constructors + public static class Person { + private final String name; + private final int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + @SuppressWarnings("unused") + public Person(String name) { + this.name = name; + this.age = 0; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + } + + @Singleton + public static class PersonCustomizer implements ObjectMapperCustomizer { + + @Override + public void customize(ObjectMapper objectMapper) { + SimpleModule module = new SimpleModule(); + module.addDeserializer(Person.class, new PersonDeserializer()); + module.addSerializer(Person.class, new PersonSerializer()); + objectMapper.registerModule(module); + } + } + + public static class PersonSerializer extends StdSerializer { + + protected PersonSerializer() { + super(Person.class); + } + + @Override + public void serialize(Person person, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("nAmE", person.getName()); + jsonGenerator.writeNumberField("aGe", person.getAge()); + jsonGenerator.writeEndObject(); + } + } + + public static class PersonDeserializer extends StdDeserializer { + + protected PersonDeserializer() { + super(Person.class); + } + + @Override + public Person deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + JsonNode node = jsonParser.getCodec().readTree(jsonParser); + String name = node.get("nAmE").asText(); + int age = (Integer) node.get("aGe").numberValue(); + + return new Person(name, age); + } + } +} diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/codecs/Codecs.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/codecs/Codecs.java index 765caeb025e88..5022b7dbd67fc 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/codecs/Codecs.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/codecs/Codecs.java @@ -11,9 +11,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.vertx.runtime.jackson.QuarkusJacksonJsonCodec; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.Json; -import io.vertx.core.json.jackson.DatabindCodec; public class Codecs { @@ -61,7 +61,7 @@ public Type getType() { }; this.clazz = null; } - this.mapper = DatabindCodec.mapper(); + this.mapper = QuarkusJacksonJsonCodec.mapper(); } @Override diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java index 1e02b5e8fef81..fa3b646350729 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java @@ -28,7 +28,7 @@ * The difference is that this class obtains the ObjectMapper from Arc in order to inherit the * user-customized ObjectMapper. */ -class QuarkusJacksonJsonCodec implements JsonCodec { +public class QuarkusJacksonJsonCodec implements JsonCodec { private static volatile ObjectMapper mapper; // we don't want to create this unless it's absolutely necessary (and it rarely is) @@ -43,7 +43,7 @@ public static void reset() { prettyMapper = null; } - private static ObjectMapper mapper() { + public static ObjectMapper mapper() { if (mapper == null) { synchronized (QuarkusJacksonJsonCodec.class) { if (mapper == null) {