diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java index 9f7bc752e092a..7265d33d8a9fd 100644 --- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java +++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java @@ -63,6 +63,7 @@ import io.quarkus.gizmo.ResultHandle; import io.quarkus.jackson.JacksonMixin; import io.quarkus.jackson.ObjectMapperCustomizer; +import io.quarkus.jackson.runtime.ConfigurationCustomizer; import io.quarkus.jackson.runtime.JacksonBuildTimeConfig; import io.quarkus.jackson.runtime.JacksonSupport; import io.quarkus.jackson.runtime.JacksonSupportRecorder; @@ -111,6 +112,8 @@ public class JacksonProcessor { @BuildStep void unremovable(Capabilities capabilities, BuildProducer producer, BuildProducer additionalProducer) { + additionalProducer.produce(AdditionalBeanBuildItem.unremovableOf(ConfigurationCustomizer.class)); + if (capabilities.isPresent(Capability.VERTX_CORE)) { producer.produce(UnremovableBeanBuildItem.beanTypes(ObjectMapper.class)); additionalProducer.produce(AdditionalBeanBuildItem.unremovableOf(VertxHybridPoolObjectMapperCustomizer.class)); diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java index d02ba0835d34d..620bb6cd051d8 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java @@ -15,6 +15,7 @@ public interface ObjectMapperCustomizer extends Comparable { int MINIMUM_PRIORITY = Integer.MIN_VALUE; + int MAXIMUM_PRIORITY = Integer.MAX_VALUE; // we use this priority to give a chance to other customizers to override serializers / deserializers // that might have been added by the modules that Quarkus registers automatically // (Jackson will keep the last registered serializer / deserializer for a given type diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ConfigurationCustomizer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ConfigurationCustomizer.java new file mode 100644 index 0000000000000..3aa6ef532010b --- /dev/null +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ConfigurationCustomizer.java @@ -0,0 +1,65 @@ +package io.quarkus.jackson.runtime; + +import java.time.ZoneId; +import java.util.TimeZone; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import io.quarkus.jackson.ObjectMapperCustomizer; + +@Singleton +public class ConfigurationCustomizer implements ObjectMapperCustomizer { + @Inject + JacksonBuildTimeConfig jacksonBuildTimeConfig; + + @Inject + JacksonSupport jacksonSupport; + + @Override + public void customize(ObjectMapper objectMapper) { + if (!jacksonBuildTimeConfig.failOnUnknownProperties) { + // this feature is enabled by default, so we disable it + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + } + if (!jacksonBuildTimeConfig.failOnEmptyBeans) { + // this feature is enabled by default, so we disable it + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + } + if (!jacksonBuildTimeConfig.writeDatesAsTimestamps) { + // this feature is enabled by default, so we disable it + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + } + if (!jacksonBuildTimeConfig.writeDurationsAsTimestamps) { + // this feature is enabled by default, so we disable it + objectMapper.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS); + } + if (jacksonBuildTimeConfig.acceptCaseInsensitiveEnums) { + objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); + } + JsonInclude.Include serializationInclusion = jacksonBuildTimeConfig.serializationInclusion.orElse(null); + if (serializationInclusion != null) { + objectMapper.setSerializationInclusion(serializationInclusion); + } + ZoneId zoneId = jacksonBuildTimeConfig.timezone.orElse(null); + if ((zoneId != null) && !zoneId.getId().equals("UTC")) { // Jackson uses UTC as the default, so let's not reset it + objectMapper.setTimeZone(TimeZone.getTimeZone(zoneId)); + } + if (jacksonSupport.configuredNamingStrategy().isPresent()) { + objectMapper.setPropertyNamingStrategy(jacksonSupport.configuredNamingStrategy().get()); + } + } + + @Override + public int priority() { + // we return the maximum possible priority to make sure these + // settings are always applied first, before any other customizers. + return ObjectMapperCustomizer.MAXIMUM_PRIORITY; + } +} diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java index 0675633a7e99e..1f1e56a540ab5 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java @@ -1,20 +1,14 @@ package io.quarkus.jackson.runtime; -import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.TimeZone; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import jakarta.inject.Singleton; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; import io.quarkus.arc.All; import io.quarkus.arc.DefaultBean; @@ -22,43 +16,12 @@ @ApplicationScoped public class ObjectMapperProducer { - @DefaultBean @Singleton @Produces public ObjectMapper objectMapper(@All List customizers, JacksonBuildTimeConfig jacksonBuildTimeConfig, JacksonSupport jacksonSupport) { ObjectMapper objectMapper = new ObjectMapper(); - if (!jacksonBuildTimeConfig.failOnUnknownProperties) { - // this feature is enabled by default, so we disable it - objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - } - if (!jacksonBuildTimeConfig.failOnEmptyBeans) { - // this feature is enabled by default, so we disable it - objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - } - if (!jacksonBuildTimeConfig.writeDatesAsTimestamps) { - // this feature is enabled by default, so we disable it - objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - } - if (!jacksonBuildTimeConfig.writeDurationsAsTimestamps) { - // this feature is enabled by default, so we disable it - objectMapper.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS); - } - if (jacksonBuildTimeConfig.acceptCaseInsensitiveEnums) { - objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS); - } - JsonInclude.Include serializationInclusion = jacksonBuildTimeConfig.serializationInclusion.orElse(null); - if (serializationInclusion != null) { - objectMapper.setSerializationInclusion(serializationInclusion); - } - ZoneId zoneId = jacksonBuildTimeConfig.timezone.orElse(null); - if ((zoneId != null) && !zoneId.getId().equals("UTC")) { // Jackson uses UTC as the default, so let's not reset it - objectMapper.setTimeZone(TimeZone.getTimeZone(zoneId)); - } - if (jacksonSupport.configuredNamingStrategy().isPresent()) { - objectMapper.setPropertyNamingStrategy(jacksonSupport.configuredNamingStrategy().get()); - } List sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers); for (ObjectMapperCustomizer customizer : sortedCustomizers) { customizer.customize(objectMapper);