From 84b553a8ca98f62bc87aa1c5f74003eeb566c8fd Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 12 Oct 2021 13:39:20 +0100 Subject: [PATCH] Upgrade to Jackson Bom 2.13.0 Closes gh-28298 --- ...ConfigurationPropertiesReportEndpoint.java | 52 +++++++++++++------ .../actuate/health/CompositeHealthTests.java | 4 +- .../JacksonAutoConfigurationTests.java | 15 +++--- .../spring-boot-dependencies/build.gradle | 2 +- .../json/JacksonTesterIntegrationTests.java | 23 ++++---- 5 files changed, 55 insertions(+), 41 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index 862a2c21ef8..99ff2500b03 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -40,6 +40,7 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; @@ -155,7 +156,9 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext private ObjectMapper getObjectMapper() { if (this.objectMapper == null) { - this.objectMapper = new ObjectMapper(); + JsonMapper.Builder builder = JsonMapper.builder(); + configureJsonMapper(builder); + this.objectMapper = builder.build(); configureObjectMapper(this.objectMapper); } return this.objectMapper; @@ -166,32 +169,47 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext * {@link ConfigurationProperties @ConfigurationProperties} objects into a {@link Map} * structure. * @param mapper the object mapper + * @deprecated since 2.6 for removal in 2.8 in favor of + * {@link #configureJsonMapper(com.fasterxml.jackson.databind.json.JsonMapper.Builder)} */ + @Deprecated protected void configureObjectMapper(ObjectMapper mapper) { - mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); - mapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true); - mapper.setSerializationInclusion(Include.NON_NULL); - applyConfigurationPropertiesFilter(mapper); - applySerializationModifier(mapper); - mapper.registerModule(new JavaTimeModule()); - } - - private void applyConfigurationPropertiesFilter(ObjectMapper mapper) { - mapper.setAnnotationIntrospector(new ConfigurationPropertiesAnnotationIntrospector()); - mapper.setFilterProvider( + + } + + /** + * Configure Jackson's {@link JsonMapper} to be used to serialize the + * {@link ConfigurationProperties @ConfigurationProperties} objects into a {@link Map} + * structure. + * @param builder the json mapper builder + * @since 2.6.0 + */ + protected void configureJsonMapper(JsonMapper.Builder builder) { + builder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + builder.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + builder.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); + JsonMapper.builder(); + builder.configure(MapperFeature.USE_STD_BEAN_NAMING, true); + builder.serializationInclusion(Include.NON_NULL); + applyConfigurationPropertiesFilter(builder); + applySerializationModifier(builder); + builder.addModule(new JavaTimeModule()); + } + + private void applyConfigurationPropertiesFilter(JsonMapper.Builder builder) { + builder.annotationIntrospector(new ConfigurationPropertiesAnnotationIntrospector()); + builder.filterProvider( new SimpleFilterProvider().setDefaultFilter(new ConfigurationPropertiesPropertyFilter())); } /** * Ensure only bindable and non-cyclic bean properties are reported. - * @param mapper the object mapper + * @param builder the JsonMapper builder */ - private void applySerializationModifier(ObjectMapper mapper) { + private void applySerializationModifier(JsonMapper.Builder builder) { SerializerFactory factory = BeanSerializerFactory.instance .withSerializerModifier(new GenericSerializerModifier()); - mapper.setSerializerFactory(factory); + builder.serializerFactory(factory); } private ContextConfigurationProperties describeBeans(ObjectMapper mapper, ApplicationContext context, diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java index 236d123d8a9..bb2b3d192b4 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthTests.java @@ -22,6 +22,7 @@ import java.util.Map; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.endpoint.ApiVersion; @@ -87,8 +88,7 @@ class CompositeHealthTests { components.put("db1", Health.up().build()); components.put("db2", Health.down().withDetail("a", "b").build()); CompositeHealth health = new CompositeHealth(ApiVersion.V2, Status.UP, components); - ObjectMapper mapper = new ObjectMapper(); - mapper.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS); + JsonMapper mapper = JsonMapper.builder().disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS).build(); String json = mapper.writeValueAsString(health); assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{\"db1\":{\"status\":\"UP\"}," + "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}}"); 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 555ca12f70a..c95797bf388 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 @@ -196,10 +196,11 @@ class JacksonAutoConfigurationTests { .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS.enabledByDefault()).isFalse(); - assertThat(mapper.getSerializationConfig() - .hasMapperFeatures(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS.getMask())).isTrue(); - assertThat(mapper.getDeserializationConfig() - .hasMapperFeatures(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS.getMask())).isTrue(); + + assertThat(mapper.getSerializationConfig().isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) + .isTrue(); + assertThat(mapper.getDeserializationConfig().isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) + .isTrue(); }); } @@ -208,10 +209,8 @@ class JacksonAutoConfigurationTests { this.contextRunner.withPropertyValues("spring.jackson.mapper.use_annotations:false").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(MapperFeature.USE_ANNOTATIONS.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig().hasMapperFeatures(MapperFeature.USE_ANNOTATIONS.getMask())) - .isFalse(); - assertThat(mapper.getSerializationConfig().hasMapperFeatures(MapperFeature.USE_ANNOTATIONS.getMask())) - .isFalse(); + assertThat(mapper.getDeserializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS)).isFalse(); + assertThat(mapper.getSerializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS)).isFalse(); }); } diff --git a/spring-boot-project/spring-boot-dependencies/build.gradle b/spring-boot-project/spring-boot-dependencies/build.gradle index b3beb68f8ca..8259031daad 100644 --- a/spring-boot-project/spring-boot-dependencies/build.gradle +++ b/spring-boot-project/spring-boot-dependencies/build.gradle @@ -536,7 +536,7 @@ bom { ] } } - library("Jackson Bom", "2.12.5") { + library("Jackson Bom", "2.13.0") { group("com.fasterxml.jackson") { imports = [ "jackson-bom" diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java index 708c46efb50..e3c78cf5e16 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -24,7 +24,7 @@ import java.util.Map; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; +import com.fasterxml.jackson.databind.json.JsonMapper; import org.junit.jupiter.api.Test; import org.springframework.core.io.ByteArrayResource; @@ -50,24 +50,18 @@ class JacksonTesterIntegrationTests { private JacksonTester stringJson; - private ObjectMapper objectMapper; - private static final String JSON = "{\"name\":\"Spring\",\"age\":123}"; - @BeforeEach - void setup() { - this.objectMapper = new ObjectMapper(); - JacksonTester.initFields(this, this.objectMapper); - } - @Test void typicalTest() throws Exception { + JacksonTester.initFields(this, new ObjectMapper()); String example = JSON; assertThat(this.simpleJson.parse(example).getObject().getName()).isEqualTo("Spring"); } @Test void typicalListTest() throws Exception { + JacksonTester.initFields(this, new ObjectMapper()); String example = "[" + JSON + "]"; assertThat(this.listJson.parse(example)).asList().hasSize(1); assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring"); @@ -75,6 +69,7 @@ class JacksonTesterIntegrationTests { @Test void typicalMapTest() throws Exception { + JacksonTester.initFields(this, new ObjectMapper()); Map map = new LinkedHashMap<>(); map.put("a", 1); map.put("b", 2); @@ -83,6 +78,7 @@ class JacksonTesterIntegrationTests { @Test void stringLiteral() throws Exception { + JacksonTester.initFields(this, new ObjectMapper()); String stringWithSpecialCharacters = "myString"; assertThat(this.stringJson.write(stringWithSpecialCharacters)).extractingJsonPathStringValue("@") .isEqualTo(stringWithSpecialCharacters); @@ -90,6 +86,7 @@ class JacksonTesterIntegrationTests { @Test void parseSpecialCharactersTest() throws Exception { + JacksonTester.initFields(this, new ObjectMapper()); // Confirms that the handling of special characters is symmetrical between // the serialization (via the JacksonTester) and the parsing (via json-path). By // default json-path uses SimpleJson as its parser, which has a slightly different @@ -103,7 +100,7 @@ class JacksonTesterIntegrationTests { @Test void writeWithView() throws Exception { - this.objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); + JacksonTester.initFields(this, JsonMapper.builder().disable(MapperFeature.DEFAULT_VIEW_INCLUSION).build()); ExampleObjectWithView object = new ExampleObjectWithView(); object.setName("Spring"); object.setAge(123); @@ -115,7 +112,7 @@ class JacksonTesterIntegrationTests { @Test void readWithResourceAndView() throws Exception { - this.objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); + JacksonTester.initFields(this, JsonMapper.builder().disable(MapperFeature.DEFAULT_VIEW_INCLUSION).build()); ByteArrayResource resource = new ByteArrayResource(JSON.getBytes()); ObjectContent content = this.jsonWithView.forView(ExampleObjectWithView.TestView.class) .read(resource); @@ -125,7 +122,7 @@ class JacksonTesterIntegrationTests { @Test void readWithReaderAndView() throws Exception { - this.objectMapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION); + JacksonTester.initFields(this, JsonMapper.builder().disable(MapperFeature.DEFAULT_VIEW_INCLUSION).build()); Reader reader = new StringReader(JSON); ObjectContent content = this.jsonWithView.forView(ExampleObjectWithView.TestView.class) .read(reader);