Move jackson read and write to spring.jackson.json

The spring.jackson.read and spring.jackson.write properties are
JSON-specific. To make this more clear, this commit moves them
beneath spring.jackson.json. This also paves the way for
spring.jackson.cbor and spring.jackson.xml properties for CBOR- and
XML-specific settings should we add auto-configuration for XMLMapper
and/or CBORMapper in the future.

Closes gh-47328
This commit is contained in:
Andy Wilkinson
2025-09-26 10:01:59 +01:00
parent 8c7e0c675f
commit 0959271061
5 changed files with 74 additions and 43 deletions
@@ -74,31 +74,31 @@ These features are described in several enums (in Jackson) that map onto propert
| `spring.jackson.datatype.json-node.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.annotation.JsonInclude$Include[]
| configprop:spring.jackson.default-property-inclusion[]
| `always`, `non_null`, `non_absent`, `non_default`, `non_empty`
|===
| javadoc:tools.jackson.databind.DeserializationFeature[]
| `spring.jackson.deserialization.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.core.JsonReadFeature[]
| `spring.jackson.json.read.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.core.JsonWriteFeature[]
| `spring.jackson.json.write.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.databind.MapperFeature[]
| `spring.jackson.mapper.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.core.JsonReadFeature[]
| `spring.jackson.read.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.core.JsonWriteFeature[]
| `spring.jackson.write.<feature_name>`
| `true`, `false`
| javadoc:tools.jackson.databind.SerializationFeature[]
| `spring.jackson.serialization.<feature_name>`
| `true`, `false`
| javadoc:com.fasterxml.jackson.annotation.JsonInclude$Include[]
| configprop:spring.jackson.default-property-inclusion[]
| `always`, `non_null`, `non_absent`, `non_default`, `non_empty`
|===
For example, to enable pretty print, set `spring.jackson.serialization.indent_output=true`.
Note that, thanks to the use of xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding], the case of `indent_output` does not have to match the case of the corresponding enum constant, which is `INDENT_OUTPUT`.
@@ -175,8 +175,8 @@ public final class JacksonAutoConfiguration {
configureFeatures(builder, this.jacksonProperties.getDeserialization(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getSerialization(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getMapper(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getRead(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getWrite(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getJson().getRead(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getJson().getWrite(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getDatatype().getDatetime(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getDatatype().getEnum(), builder::configure);
configureFeatures(builder, this.jacksonProperties.getDatatype().getJsonNode(), builder::configure);
@@ -81,16 +81,6 @@ public class JacksonProperties {
*/
private final Map<MapperFeature, Boolean> mapper = new EnumMap<>(MapperFeature.class);
/**
* Jackson on/off features for readers.
*/
private final Map<JsonReadFeature, Boolean> read = new EnumMap<>(JsonReadFeature.class);
/**
* Jackson on/off features for writers.
*/
private final Map<JsonWriteFeature, Boolean> write = new EnumMap<>(JsonWriteFeature.class);
/**
* Controls the inclusion of properties during serialization. Configured with one of
* the values in Jackson's JsonInclude.Include enumeration.
@@ -121,6 +111,8 @@ public class JacksonProperties {
private final Datatype datatype = new Datatype();
private final Json json = new Json();
public @Nullable String getDateFormat() {
return this.dateFormat;
}
@@ -153,14 +145,6 @@ public class JacksonProperties {
return this.mapper;
}
public Map<JsonReadFeature, Boolean> getRead() {
return this.read;
}
public Map<JsonWriteFeature, Boolean> getWrite() {
return this.write;
}
public JsonInclude.@Nullable Include getDefaultPropertyInclusion() {
return this.defaultPropertyInclusion;
}
@@ -205,6 +189,10 @@ public class JacksonProperties {
return this.datatype;
}
public Json getJson() {
return this.json;
}
public enum ConstructorDetectorStrategy {
/**
@@ -261,4 +249,26 @@ public class JacksonProperties {
}
public static class Json {
/**
* Jackson on/off token reader features that are specific to JSON.
*/
private final Map<JsonReadFeature, Boolean> read = new EnumMap<>(JsonReadFeature.class);
/**
* Jackson on/off token writer features that are specific to JSON.
*/
private final Map<JsonWriteFeature, Boolean> write = new EnumMap<>(JsonWriteFeature.class);
public Map<JsonReadFeature, Boolean> getRead() {
return this.read;
}
public Map<JsonWriteFeature, Boolean> getWrite() {
return this.write;
}
}
}
@@ -15,6 +15,26 @@
"deprecation": {
"level": "error"
}
},
{
"name": "spring.jackson.read",
"type": "java.util.Map<tools.jackson.core.json.JsonReadFeature,java.lang.Boolean>",
"description": "Jackson on/off token reader features that are specific to JSON.",
"sourceType": "org.springframework.boot.jackson.autoconfigure.JacksonProperties",
"deprecation": {
"level": "error",
"replacement": "spring.jackson.json.read"
}
},
{
"name": "spring.jackson.write",
"type": "java.util.Map<tools.jackson.core.json.JsonWriteFeature,java.lang.Boolean>",
"description": "Jackson on/off token writer features that are specific to JSON.",
"sourceType": "org.springframework.boot.jackson.autoconfigure.JacksonProperties",
"deprecation": {
"level": "error",
"replacement": "spring.jackson.json.write"
}
}
]
}
@@ -237,8 +237,8 @@ class JacksonAutoConfigurationTests {
}
@Test
void enableReadFeature() {
this.contextRunner.withPropertyValues("spring.jackson.read.allow_single_quotes:true").run((context) -> {
void enableJsonReadFeature() {
this.contextRunner.withPropertyValues("spring.jackson.json.read.allow_single_quotes:true").run((context) -> {
JsonMapper mapper = context.getBean(JsonMapper.class);
assertThat(JsonReadFeature.ALLOW_SINGLE_QUOTES.enabledByDefault()).isFalse();
assertThat(mapper.isEnabled(JsonReadFeature.ALLOW_SINGLE_QUOTES)).isTrue();
@@ -246,18 +246,19 @@ class JacksonAutoConfigurationTests {
}
@Test
void enableWriteFeature() {
this.contextRunner.withPropertyValues("spring.jackson.write.write_numbers_as_strings:true").run((context) -> {
JsonMapper mapper = context.getBean(JsonMapper.class);
JsonWriteFeature feature = JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS;
assertThat(feature.enabledByDefault()).isFalse();
assertThat(mapper.isEnabled(feature)).isTrue();
});
void enableJsonWriteFeature() {
this.contextRunner.withPropertyValues("spring.jackson.json.write.write_numbers_as_strings:true")
.run((context) -> {
JsonMapper mapper = context.getBean(JsonMapper.class);
JsonWriteFeature feature = JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS;
assertThat(feature.enabledByDefault()).isFalse();
assertThat(mapper.isEnabled(feature)).isTrue();
});
}
@Test
void disableWriteFeature() {
this.contextRunner.withPropertyValues("spring.jackson.write.write_hex_upper_case:false").run((context) -> {
this.contextRunner.withPropertyValues("spring.jackson.json.write.write_hex_upper_case:false").run((context) -> {
JsonMapper mapper = context.getBean(JsonMapper.class);
assertThat(JsonWriteFeature.WRITE_HEX_UPPER_CASE.enabledByDefault()).isTrue();
assertThat(mapper.isEnabled(JsonWriteFeature.WRITE_HEX_UPPER_CASE)).isFalse();