Skip to content

Commit 6e4b88d

Browse files
committed
Add property to configure Jackson 3 with Boot's Jackson 2 defaults
Closes gh-
1 parent 630797f commit 6e4b88d

4 files changed

Lines changed: 74 additions & 0 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ Note that, thanks to the use of xref:reference:features/external-config.adoc#fea
104104

105105
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.
106106

107+
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`.
108+
107109
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.
108110
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.
109111

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import tools.jackson.databind.PropertyNamingStrategies;
3737
import tools.jackson.databind.PropertyNamingStrategy;
3838
import tools.jackson.databind.cfg.ConstructorDetector;
39+
import tools.jackson.databind.cfg.DateTimeFeature;
3940
import tools.jackson.databind.json.JsonMapper;
4041

4142
import org.springframework.aot.hint.ReflectionHints;
@@ -164,6 +165,11 @@ public int getOrder() {
164165

165166
@Override
166167
public void customize(JsonMapper.Builder builder) {
168+
if (this.jacksonProperties.isUseJackson2Defaults()) {
169+
builder.configureForJackson2()
170+
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS,
171+
DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS);
172+
}
167173
if (this.jacksonProperties.getDefaultPropertyInclusion() != null) {
168174
builder.changeDefaultPropertyInclusion((handler) -> handler
169175
.withValueInclusion(this.jacksonProperties.getDefaultPropertyInclusion()));

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ public class JacksonProperties {
109109
*/
110110
private @Nullable Locale locale;
111111

112+
/**
113+
* Whether to configure Jackson 3 with the same defaults as Spring Boot previously
114+
* used for Jackson 2.
115+
*/
116+
private boolean useJackson2Defaults = false;
117+
112118
private final Datatype datatype = new Datatype();
113119

114120
private final Json json = new Json();
@@ -185,6 +191,14 @@ public void setLocale(@Nullable Locale locale) {
185191
this.locale = locale;
186192
}
187193

194+
public boolean isUseJackson2Defaults() {
195+
return this.useJackson2Defaults;
196+
}
197+
198+
public void setUseJackson2Defaults(boolean useJackson2Defaults) {
199+
this.useJackson2Defaults = useJackson2Defaults;
200+
}
201+
188202
public Datatype getDatatype() {
189203
return this.datatype;
190204
}

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.jspecify.annotations.Nullable;
3030
import org.junit.jupiter.api.Test;
3131
import tools.jackson.core.JsonGenerator;
32+
import tools.jackson.core.StreamReadFeature;
33+
import tools.jackson.core.StreamWriteFeature;
3234
import tools.jackson.core.json.JsonReadFeature;
3335
import tools.jackson.core.json.JsonWriteFeature;
3436
import tools.jackson.databind.DeserializationFeature;
@@ -539,6 +541,56 @@ void shouldRegisterProblemDetailsMixin() {
539541
});
540542
}
541543

544+
@Test
545+
void whenUsingJackson2DefaultsShouldBeConfiguredUsingConfigureForJackson2() {
546+
this.contextRunner.withPropertyValues("spring.jackson.use-jackson2-defaults=true").run((context) -> {
547+
JsonMapper jsonMapper = context.getBean(JsonMapper.class);
548+
JsonMapper jackson2ConfiguredJsonMapper = JsonMapper.builder().configureForJackson2().build();
549+
for (DateTimeFeature feature : DateTimeFeature.values()) {
550+
boolean expected = (feature == DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS
551+
|| feature == DateTimeFeature.WRITE_DURATIONS_AS_TIMESTAMPS) ? false
552+
: jackson2ConfiguredJsonMapper.isEnabled(feature);
553+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name()).isEqualTo(expected);
554+
}
555+
for (DeserializationFeature feature : DeserializationFeature.values()) {
556+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
557+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
558+
}
559+
for (EnumFeature feature : EnumFeature.values()) {
560+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
561+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
562+
}
563+
for (JsonNodeFeature feature : JsonNodeFeature.values()) {
564+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
565+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
566+
}
567+
for (JsonReadFeature feature : JsonReadFeature.values()) {
568+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
569+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
570+
}
571+
for (JsonWriteFeature feature : JsonWriteFeature.values()) {
572+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
573+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
574+
}
575+
for (MapperFeature feature : MapperFeature.values()) {
576+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
577+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
578+
}
579+
for (SerializationFeature feature : SerializationFeature.values()) {
580+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
581+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
582+
}
583+
for (StreamReadFeature feature : StreamReadFeature.values()) {
584+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
585+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
586+
}
587+
for (StreamWriteFeature feature : StreamWriteFeature.values()) {
588+
assertThat(jsonMapper.isEnabled(feature)).as(feature.name())
589+
.isEqualTo(jackson2ConfiguredJsonMapper.isEnabled(feature));
590+
}
591+
});
592+
}
593+
542594
static class MyDateFormat extends SimpleDateFormat {
543595

544596
MyDateFormat() {

0 commit comments

Comments
 (0)