Browse Source

Add property to configure Jackson 3 with Boot's Jackson 2 defaults

Closes gh-
pull/47478/head
Andy Wilkinson 2 months ago
parent
commit
6e4b88d850
  1. 2
      documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc
  2. 6
      module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java
  3. 14
      module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java
  4. 52
      module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java

2
documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc

@ -104,6 +104,8 @@ Note that, thanks to the use of xref:reference:features/external-config.adoc#fea @@ -104,6 +104,8 @@ Note that, thanks to the use of xref:reference:features/external-config.adoc#fea
This environment-based configuration is applied to the auto-configured javadoc:tools.jackson.databind.json.JsonMapper.Builder[] bean and applies to any mappers created by using the builder, including the auto-configured javadoc:tools.jackson.databind.json.JsonMapper[] bean.
To ease the migration when working on an application that previously used Jackson 2, the auto-configured `JsonMapper` can be configured to use defaults that are as close as possible to those that Spring Boot used for Jackson 2. To enable these defaults, set configprop:spring.jackson.use-jackson2-defaults[] to `true`.
The context's javadoc:tools.jackson.databind.json.JsonMapper.Builder[] can be customized by one or more javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans.
Such customizer beans can be ordered (Boot's own customizer has an order of 0), letting additional customization be applied both before and after Boot's customization.

6
module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java

@ -36,6 +36,7 @@ import tools.jackson.databind.ObjectMapper; @@ -36,6 +36,7 @@ import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.PropertyNamingStrategies;
import tools.jackson.databind.PropertyNamingStrategy;
import tools.jackson.databind.cfg.ConstructorDetector;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aot.hint.ReflectionHints;
@ -164,6 +165,11 @@ public final class JacksonAutoConfiguration { @@ -164,6 +165,11 @@ public final class JacksonAutoConfiguration {
@Override
public void customize(JsonMapper.Builder builder) {
if (this.jacksonProperties.isUseJackson2Defaults()) {
builder.configureForJackson2()
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS,
DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS);
}
if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
builder.changeDefaultPropertyInclusion((handler) -> handler
.withValueInclusion(this.jacksonProperties.getDefaultPropertyInclusion()));

14
module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java

@ -109,6 +109,12 @@ public class JacksonProperties { @@ -109,6 +109,12 @@ public class JacksonProperties {
*/
private @Nullable Locale locale;
/**
* Whether to configure Jackson 3 with the same defaults as Spring Boot previously
* used for Jackson 2.
*/
private boolean useJackson2Defaults = false;
private final Datatype datatype = new Datatype();
private final Json json = new Json();
@ -185,6 +191,14 @@ public class JacksonProperties { @@ -185,6 +191,14 @@ public class JacksonProperties {
this.locale = locale;
}
public boolean isUseJackson2Defaults() {
return this.useJackson2Defaults;
}
public void setUseJackson2Defaults(boolean useJackson2Defaults) {
this.useJackson2Defaults = useJackson2Defaults;
}
public Datatype getDatatype() {
return this.datatype;
}

52
module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java

@ -29,6 +29,8 @@ import org.assertj.core.api.InstanceOfAssertFactories; @@ -29,6 +29,8 @@ import org.assertj.core.api.InstanceOfAssertFactories;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.StreamReadFeature;
import tools.jackson.core.StreamWriteFeature;
import tools.jackson.core.json.JsonReadFeature;
import tools.jackson.core.json.JsonWriteFeature;
import tools.jackson.databind.DeserializationFeature;
@ -539,6 +541,56 @@ class JacksonAutoConfigurationTests { @@ -539,6 +541,56 @@ class JacksonAutoConfigurationTests {
});
}
@Test
void whenUsingJackson2DefaultsShouldBeConfiguredUsingConfigureForJackson2() {
this.contextRunner.withPropertyValues("spring.jackson.use-jackson2-defaults=true").run((context) -> {
JsonMapper jsonMapper = context.getBean(JsonMapper.class);
JsonMapper jackson2ConfiguredJsonMapper = JsonMapper.builder().configureForJackson2().build();
for (DateTimeFeature feature : DateTimeFeature.values()) {
boolean expected = (feature == DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS
|| feature == DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS) ? false
: jackson2ConfiguredJsonMapper.isEnabled(feature);
assertThat(jsonMapper.isEnabled(feature)).as(feature.name()).isEqualTo(expected);
}
for (DeserializationFeature feature : DeserializationFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (EnumFeature feature : EnumFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (JsonNodeFeature feature : JsonNodeFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (JsonReadFeature feature : JsonReadFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (JsonWriteFeature feature : JsonWriteFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (MapperFeature feature : MapperFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (SerializationFeature feature : SerializationFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (StreamReadFeature feature : StreamReadFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
for (StreamWriteFeature feature : StreamWriteFeature.values()) {
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
}
});
}
static class MyDateFormat extends SimpleDateFormat {
MyDateFormat() {

Loading…
Cancel
Save