From 8edb4b97292e59b0b96a8deecd3b058e94b744a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Fri, 13 Oct 2023 21:16:43 -0600 Subject: [PATCH 1/2] Add properties for configuring EnumFeature and JsonNodeFeature Both `EnumFeature` and `JsonNodeFeature` implement `DataTypeFeature` which was recently added in Spring Framework. This commits introduces support to allow the configuration via properties. See spring-projects/spring-framework#31380 See gh-37885 --- .../jackson/JacksonAutoConfiguration.java | 2 ++ .../jackson/JacksonProperties.java | 23 +++++++++++++++++- .../JacksonAutoConfigurationTests.java | 24 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index a6aa97773ed..68cc6f27e02 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -213,6 +213,8 @@ public class JacksonAutoConfiguration { configureFeatures(builder, this.jacksonProperties.getMapper()); configureFeatures(builder, this.jacksonProperties.getParser()); configureFeatures(builder, this.jacksonProperties.getGenerator()); + configureFeatures(builder, this.jacksonProperties.getEnumDatatype()); + configureFeatures(builder, this.jacksonProperties.getJsonNodeDatatype()); configureDateFormat(builder); configurePropertyNamingStrategy(builder); configureModules(builder); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java index 805604e9830..a886d7a9224 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.cfg.EnumFeature; +import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -38,6 +40,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * @author Andy Wilkinson * @author Marcel Overdijk * @author Johannes Edmeier + * @author Eddú Meléndez * @since 1.2.0 */ @ConfigurationProperties(prefix = "spring.jackson") @@ -86,6 +89,16 @@ public class JacksonProperties { */ private final Map generator = new EnumMap<>(JsonGenerator.Feature.class); + /** + * Jackson on/off features for enum types. + */ + private final Map enumDatatype = new EnumMap<>(EnumFeature.class); + + /** + * Jackson on/off features for JsonNode types. + */ + private final Map jsonNodeDatatype = new EnumMap<>(JsonNodeFeature.class); + /** * Controls the inclusion of properties during serialization. Configured with one of * the values in Jackson's JsonInclude.Include enumeration. @@ -154,6 +167,14 @@ public class JacksonProperties { return this.generator; } + public Map getEnumDatatype() { + return this.enumDatatype; + } + + public Map getJsonNodeDatatype() { + return this.jsonNodeDatatype; + } + public JsonInclude.Include getDefaultPropertyInclusion() { return this.defaultPropertyInclusion; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index 3c14adc3ef3..3528fc8afd6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -45,6 +45,8 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.cfg.ConstructorDetector; import com.fasterxml.jackson.databind.cfg.ConstructorDetector.SingleArgConstructor; +import com.fasterxml.jackson.databind.cfg.EnumFeature; +import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.util.StdDateFormat; @@ -88,6 +90,7 @@ import static org.mockito.Mockito.mock; * @author Johannes Edmeier * @author Grzegorz Poznachowski * @author Ralf Ueberfuhr + * @author Eddú Meléndez */ class JacksonAutoConfigurationTests { @@ -289,6 +292,27 @@ class JacksonAutoConfigurationTests { }); } + @Test + void enableEnumFeature() { + this.contextRunner.withPropertyValues("spring.jackson.enum-data-type.write_enums_to_lowercase:true") + .run((context) -> { + ObjectMapper mapper = context.getBean(ObjectMapper.class); + assertThat(EnumFeature.WRITE_ENUMS_TO_LOWERCASE.enabledByDefault()).isFalse(); + assertThat(mapper.getSerializationConfig().isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)).isTrue(); + }); + } + + @Test + void disableJsonNodeFeature() { + this.contextRunner.withPropertyValues("spring.jackson.json-node-data-type.write_null_properties:false") + .run((context) -> { + ObjectMapper mapper = context.getBean(ObjectMapper.class); + assertThat(JsonNodeFeature.WRITE_NULL_PROPERTIES.enabledByDefault()).isTrue(); + assertThat(mapper.getDeserializationConfig().isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES)) + .isFalse(); + }); + } + @Test void moduleBeansAndWellKnownModulesAreRegisteredWithTheObjectMapperBuilder() { this.contextRunner.withUserConfiguration(ModuleConfig.class).run((context) -> { From d796087dfae4f15f1f893f70fd8bb72ceb25098c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 16 Oct 2023 15:12:01 +0100 Subject: [PATCH 2/2] Polish "Add properties for configuring EnumFeature and JsonNodeFeature" See gh-37885 --- .../jackson/JacksonAutoConfiguration.java | 4 +- .../jackson/JacksonProperties.java | 46 +++++++++++-------- ...itional-spring-configuration-metadata.json | 4 ++ .../JacksonAutoConfigurationTests.java | 4 +- .../src/docs/asciidoc/howto/spring-mvc.adoc | 10 +++- 5 files changed, 45 insertions(+), 23 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index 68cc6f27e02..5410786e438 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -213,8 +213,8 @@ public class JacksonAutoConfiguration { configureFeatures(builder, this.jacksonProperties.getMapper()); configureFeatures(builder, this.jacksonProperties.getParser()); configureFeatures(builder, this.jacksonProperties.getGenerator()); - configureFeatures(builder, this.jacksonProperties.getEnumDatatype()); - configureFeatures(builder, this.jacksonProperties.getJsonNodeDatatype()); + configureFeatures(builder, this.jacksonProperties.getDatatype().getEnum()); + configureFeatures(builder, this.jacksonProperties.getDatatype().getJsonNode()); configureDateFormat(builder); configurePropertyNamingStrategy(builder); configureModules(builder); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java index a886d7a9224..fe3a67e028a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonProperties.java @@ -89,16 +89,6 @@ public class JacksonProperties { */ private final Map generator = new EnumMap<>(JsonGenerator.Feature.class); - /** - * Jackson on/off features for enum types. - */ - private final Map enumDatatype = new EnumMap<>(EnumFeature.class); - - /** - * Jackson on/off features for JsonNode types. - */ - private final Map jsonNodeDatatype = new EnumMap<>(JsonNodeFeature.class); - /** * Controls the inclusion of properties during serialization. Configured with one of * the values in Jackson's JsonInclude.Include enumeration. @@ -127,6 +117,8 @@ public class JacksonProperties { */ private Locale locale; + private final Datatype datatype = new Datatype(); + public String getDateFormat() { return this.dateFormat; } @@ -167,14 +159,6 @@ public class JacksonProperties { return this.generator; } - public Map getEnumDatatype() { - return this.enumDatatype; - } - - public Map getJsonNodeDatatype() { - return this.jsonNodeDatatype; - } - public JsonInclude.Include getDefaultPropertyInclusion() { return this.defaultPropertyInclusion; } @@ -215,6 +199,10 @@ public class JacksonProperties { this.locale = locale; } + public Datatype getDatatype() { + return this.datatype; + } + public enum ConstructorDetectorStrategy { /** @@ -240,4 +228,26 @@ public class JacksonProperties { } + public static class Datatype { + + /** + * Jackson on/off features for enums. + */ + private final Map enumFeatures = new EnumMap<>(EnumFeature.class); + + /** + * Jackson on/off features for JsonNodes. + */ + private final Map jsonNode = new EnumMap<>(JsonNodeFeature.class); + + public Map getEnum() { + return this.enumFeatures; + } + + public Map getJsonNode() { + return this.jsonNode; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 5f8c301fd27..7d21dfd05e2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1559,6 +1559,10 @@ "name": "spring.jackson.constructor-detector", "defaultValue": "default" }, + { + "name": "spring.jackson.datatype.enum", + "description": "Jackson on/off features for enums." + }, { "name": "spring.jackson.joda-date-time-format", "type": "java.lang.String", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index 3528fc8afd6..d11ba22e93b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -294,7 +294,7 @@ class JacksonAutoConfigurationTests { @Test void enableEnumFeature() { - this.contextRunner.withPropertyValues("spring.jackson.enum-data-type.write_enums_to_lowercase:true") + this.contextRunner.withPropertyValues("spring.jackson.datatype.enum.write-enums-to-lowercase=true") .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(EnumFeature.WRITE_ENUMS_TO_LOWERCASE.enabledByDefault()).isFalse(); @@ -304,7 +304,7 @@ class JacksonAutoConfigurationTests { @Test void disableJsonNodeFeature() { - this.contextRunner.withPropertyValues("spring.jackson.json-node-data-type.write_null_properties:false") + this.contextRunner.withPropertyValues("spring.jackson.datatype.jsonnode.write-null-properties:false") .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(JsonNodeFeature.WRITE_NULL_PROPERTIES.enabledByDefault()).isTrue(); diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc index 038d07338e5..a5a4911773f 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/spring-mvc.adoc @@ -64,11 +64,19 @@ Spring Boot also has some features to make it easier to customize this behavior. You can configure the `ObjectMapper` and `XmlMapper` instances by using the environment. Jackson provides an extensive suite of on/off features that can be used to configure various aspects of its processing. -These features are described in six enums (in Jackson) that map onto properties in the environment: +These features are described in several enums (in Jackson) that map onto properties in the environment: |=== | Enum | Property | Values +| `com.fasterxml.jackson.databind.cfg.EnumFeature` +| `spring.jackson.datatype.enum.` +| `true`, `false` + +| `com.fasterxml.jackson.databind.cfg.JsonNodeFeature` +| `spring.jackson.datatype.jsonnode.` +| `true`, `false` + | `com.fasterxml.jackson.databind.DeserializationFeature` | `spring.jackson.deserialization.` | `true`, `false`