Skip to content

Commit d9fc89b

Browse files
committed
Add Jackson 2 modules and support
Closes gh-47688
2 parents 63523c5 + e7f3ae1 commit d9fc89b

File tree

109 files changed

+6603
-348
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+6603
-348
lines changed

buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ private void addVersionAttributes(Map<String, String> attributes, Map<String, St
165165
addDependencyVersion(attributes, "jackson-core", "tools.jackson.core:jackson-core");
166166
addDependencyVersion(attributes, "jackson-databind", "tools.jackson.core:jackson-databind");
167167
addDependencyVersion(attributes, "jackson-dataformat-xml", "tools.jackson.dataformat:jackson-dataformat-xml");
168+
addDependencyVersion(attributes, "jackson2-databind", "com.fasterxml.jackson.core:jackson-databind");
168169
addSpringDataDependencyVersion(attributes, internal, "spring-data-commons");
169170
addSpringDataDependencyVersion(attributes, internal, "spring-data-couchbase");
170171
addSpringDataDependencyVersion(attributes, internal, "spring-data-cassandra");

buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,12 @@ url-jackson-annotations-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.cor
8080
url-jackson-core-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-core/{version-jackson-core}
8181
url-jackson-databind-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-databind/{version-jackson-databind}
8282
url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml}
83+
url-jackson2-databind-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson2-databind}
8384

8485
# === Javadoc Locations ===
8586

8687
javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc}
88+
javadoc-location-com-fasterxml-jackson-databind={url-jackson2-databind-javadoc}
8789
javadoc-location-org-apache-pulsar-client-api={url-pulsar-client-api-javadoc}
8890
javadoc-location-org-apache-pulsar-reactive-client-api={url-pulsar-client-reactive-api-javadoc}
8991
javadoc-location-org-springframework-data-cassandra={url-spring-data-cassandra-javadoc}

buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,10 @@ private Map<String, String> mockDependencyVersions(String version) {
282282
addMockTestcontainersVersion(versions, "redpanda", version);
283283
addMockTestcontainersVersion(versions, "r2dbc", version);
284284
addMockJackson2CoreVersion(versions, "jackson-annotations", version);
285+
addMockJackson2CoreVersion(versions, "jackson-databind", version);
285286
addMockJacksonCoreVersion(versions, "jackson-core", version);
286287
addMockJacksonCoreVersion(versions, "jackson-databind", version);
288+
addMockJacksonCoreVersion(versions, "jackson-databind", version);
287289
versions.put("org.apache.pulsar:pulsar-client-api", version);
288290
versions.put("org.apache.pulsar:pulsar-client-reactive-api", version);
289291
versions.put("tools.jackson.dataformat:jackson-dataformat-xml", version);

core/spring-boot-test/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dependencies {
2727
api(project(":core:spring-boot"))
2828
api("org.springframework:spring-test")
2929

30-
optional("tools.jackson.core:jackson-databind")
30+
optional("com.fasterxml.jackson.core:jackson-databind")
3131
optional("com.google.code.gson:gson")
3232
optional("com.jayway.jsonpath:json-path")
3333
optional("jakarta.json.bind:jakarta.json.bind-api")
@@ -45,6 +45,7 @@ dependencies {
4545
optional("org.seleniumhq.selenium:selenium-api")
4646
optional("org.skyscreamer:jsonassert")
4747
optional("org.springframework:spring-web")
48+
optional("tools.jackson.core:jackson-databind")
4849

4950
testImplementation(project(":test-support:spring-boot-test-support"))
5051
testImplementation("ch.qos.logback:logback-classic")
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.json;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.Reader;
22+
23+
import com.fasterxml.jackson.databind.JavaType;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.fasterxml.jackson.databind.ObjectReader;
26+
import com.fasterxml.jackson.databind.ObjectWriter;
27+
import com.jayway.jsonpath.Configuration;
28+
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
29+
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
30+
import org.jspecify.annotations.Nullable;
31+
32+
import org.springframework.beans.factory.ObjectFactory;
33+
import org.springframework.core.ResolvableType;
34+
import org.springframework.util.Assert;
35+
36+
/**
37+
* AssertJ based JSON tester backed by Jackson 2. Usually instantiated via
38+
* {@link #initFields(Object, ObjectMapper)}, for example: <pre class="code">
39+
* public class ExampleObjectJsonTests {
40+
*
41+
* private Jackson2Tester&lt;ExampleObject&gt; json;
42+
*
43+
* &#064;Before
44+
* public void setup() {
45+
* ObjectMapper objectMapper = new ObjectMapper();
46+
* Jackson2Tester.initFields(this, objectMapper);
47+
* }
48+
*
49+
* &#064;Test
50+
* public void testWriteJson() throws IOException {
51+
* ExampleObject object = //...
52+
* assertThat(json.write(object)).isEqualToJson("expected.json");
53+
* }
54+
*
55+
* }
56+
* </pre>
57+
*
58+
* See {@link AbstractJsonMarshalTester} for more details.
59+
*
60+
* @param <T> the type under test
61+
* @author Phillip Webb
62+
* @author Madhura Bhave
63+
* @author Diego Berrueta
64+
* @since 4.0.0
65+
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of Jackson 3.
66+
*/
67+
@Deprecated(since = "4.0.0", forRemoval = true)
68+
public class Jackson2Tester<T> extends AbstractJsonMarshalTester<T> {
69+
70+
private final ObjectMapper objectMapper;
71+
72+
private @Nullable Class<?> view;
73+
74+
/**
75+
* Create a new {@link Jackson2Tester} instance.
76+
* @param objectMapper the Jackson object mapper
77+
*/
78+
protected Jackson2Tester(ObjectMapper objectMapper) {
79+
Assert.notNull(objectMapper, "'objectMapper' must not be null");
80+
this.objectMapper = objectMapper;
81+
}
82+
83+
/**
84+
* Create a new {@link Jackson2Tester} instance.
85+
* @param resourceLoadClass the source class used to load resources
86+
* @param type the type under test
87+
* @param objectMapper the Jackson object mapper
88+
*/
89+
public Jackson2Tester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper) {
90+
this(resourceLoadClass, type, objectMapper, null);
91+
}
92+
93+
/**
94+
* Create a new {@link Jackson2Tester} instance.
95+
* @param resourceLoadClass the source class used to load resources
96+
* @param type the type under test
97+
* @param objectMapper the Jackson object mapper
98+
* @param view the JSON view
99+
*/
100+
public Jackson2Tester(Class<?> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper,
101+
@Nullable Class<?> view) {
102+
super(resourceLoadClass, type);
103+
Assert.notNull(objectMapper, "'objectMapper' must not be null");
104+
this.objectMapper = objectMapper;
105+
this.view = view;
106+
}
107+
108+
@Override
109+
protected JsonContent<T> getJsonContent(String json) {
110+
Configuration configuration = Configuration.builder()
111+
.jsonProvider(new JacksonJsonProvider(this.objectMapper))
112+
.mappingProvider(new JacksonMappingProvider(this.objectMapper))
113+
.build();
114+
Class<?> resourceLoadClass = getResourceLoadClass();
115+
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
116+
return new JsonContent<>(resourceLoadClass, getType(), json, configuration);
117+
}
118+
119+
@Override
120+
protected T readObject(InputStream inputStream, ResolvableType type) throws IOException {
121+
return getObjectReader(type).readValue(inputStream);
122+
}
123+
124+
@Override
125+
protected T readObject(Reader reader, ResolvableType type) throws IOException {
126+
return getObjectReader(type).readValue(reader);
127+
}
128+
129+
private ObjectReader getObjectReader(ResolvableType type) {
130+
ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
131+
if (this.view != null) {
132+
return objectReader.withView(this.view);
133+
}
134+
return objectReader;
135+
}
136+
137+
@Override
138+
protected String writeObject(T value, ResolvableType type) throws IOException {
139+
return getObjectWriter(type).writeValueAsString(value);
140+
}
141+
142+
private ObjectWriter getObjectWriter(ResolvableType type) {
143+
ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
144+
if (this.view != null) {
145+
return objectWriter.withView(this.view);
146+
}
147+
return objectWriter;
148+
}
149+
150+
private JavaType getType(ResolvableType type) {
151+
return this.objectMapper.constructType(type.getType());
152+
}
153+
154+
/**
155+
* Utility method to initialize {@link Jackson2Tester} fields. See
156+
* {@link Jackson2Tester class-level documentation} for example usage.
157+
* @param testInstance the test instance
158+
* @param objectMapper the JSON mapper
159+
* @see #initFields(Object, ObjectMapper)
160+
*/
161+
public static void initFields(Object testInstance, ObjectMapper objectMapper) {
162+
new Jackson2FieldInitializer().initFields(testInstance, objectMapper);
163+
}
164+
165+
/**
166+
* Utility method to initialize {@link Jackson2Tester} fields. See
167+
* {@link Jackson2Tester class-level documentation} for example usage.
168+
* @param testInstance the test instance
169+
* @param objectMapperFactory a factory to create the object mapper
170+
* @see #initFields(Object, ObjectMapper)
171+
*/
172+
public static void initFields(Object testInstance, ObjectFactory<ObjectMapper> objectMapperFactory) {
173+
new Jackson2FieldInitializer().initFields(testInstance, objectMapperFactory);
174+
}
175+
176+
/**
177+
* Returns a new instance of {@link Jackson2Tester} with the view that should be used
178+
* for json serialization/deserialization.
179+
* @param view the view class
180+
* @return the new instance
181+
*/
182+
public Jackson2Tester<T> forView(Class<?> view) {
183+
Class<?> resourceLoadClass = getResourceLoadClass();
184+
ResolvableType type = getType();
185+
Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
186+
Assert.state(type != null, "'type' must not be null");
187+
return new Jackson2Tester<>(resourceLoadClass, type, this.objectMapper, view);
188+
}
189+
190+
/**
191+
* {@link FieldInitializer} for Jackson.
192+
*/
193+
private static class Jackson2FieldInitializer extends FieldInitializer<ObjectMapper> {
194+
195+
protected Jackson2FieldInitializer() {
196+
super(Jackson2Tester.class);
197+
}
198+
199+
@Override
200+
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type,
201+
ObjectMapper marshaller) {
202+
return new Jackson2Tester<>(resourceLoadClass, type, marshaller);
203+
}
204+
205+
}
206+
207+
}

core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper
103103
this(resourceLoadClass, type, jsonMapper, null);
104104
}
105105

106+
/**
107+
* Create a new {@link JacksonTester} instance.
108+
* @param resourceLoadClass the source class used to load resources
109+
* @param type the type under test
110+
* @param jsonMapper the Jackson JSON mapper
111+
* @param view the JSON view
112+
* @since 4.0.0
113+
*/
106114
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper,
107115
@Nullable Class<?> view) {
108116
super(resourceLoadClass, type);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.json;
18+
19+
import java.util.List;
20+
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import org.jspecify.annotations.Nullable;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.core.ResolvableType;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
29+
30+
/**
31+
* Tests for {@link Jackson2Tester}.
32+
*
33+
* @author Phillip Webb
34+
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of JacksonTesterTests
35+
*/
36+
@Deprecated(since = "4.0.0", forRemoval = true)
37+
@SuppressWarnings("removal")
38+
class Jackson2TesterTests extends AbstractJsonMarshalTesterTests {
39+
40+
@Test
41+
@SuppressWarnings("NullAway") // Test null check
42+
void initFieldsWhenTestIsNullShouldThrowException() {
43+
assertThatIllegalArgumentException().isThrownBy(() -> Jackson2Tester.initFields(null, new ObjectMapper()))
44+
.withMessageContaining("'testInstance' must not be null");
45+
}
46+
47+
@Test
48+
@SuppressWarnings("NullAway") // Test null check
49+
void initFieldsWhenMarshallerIsNullShouldThrowException() {
50+
assertThatIllegalArgumentException()
51+
.isThrownBy(() -> Jackson2Tester.initFields(new InitFieldsTestClass(), (ObjectMapper) null))
52+
.withMessageContaining("'marshaller' must not be null");
53+
}
54+
55+
@Test
56+
void initFieldsShouldSetNullFields() {
57+
InitFieldsTestClass test = new InitFieldsTestClass();
58+
assertThat(test.test).isNull();
59+
assertThat(test.base).isNull();
60+
Jackson2Tester.initFields(test, new ObjectMapper());
61+
assertThat(test.test).isNotNull();
62+
assertThat(test.base).isNotNull();
63+
ResolvableType type = test.test.getType();
64+
assertThat(type).isNotNull();
65+
assertThat(type.resolve()).isEqualTo(List.class);
66+
assertThat(type.resolveGeneric()).isEqualTo(ExampleObject.class);
67+
}
68+
69+
@Override
70+
protected AbstractJsonMarshalTester<Object> createTester(Class<?> resourceLoadClass, ResolvableType type) {
71+
return new org.springframework.boot.test.json.Jackson2Tester<>(resourceLoadClass, type, new ObjectMapper());
72+
}
73+
74+
abstract static class InitFieldsBaseClass {
75+
76+
public org.springframework.boot.test.json.@Nullable Jackson2Tester<ExampleObject> base;
77+
78+
public org.springframework.boot.test.json.Jackson2Tester<ExampleObject> baseSet = new org.springframework.boot.test.json.Jackson2Tester<>(
79+
InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
80+
81+
}
82+
83+
static class InitFieldsTestClass extends InitFieldsBaseClass {
84+
85+
public org.springframework.boot.test.json.@Nullable Jackson2Tester<List<ExampleObject>> test;
86+
87+
public org.springframework.boot.test.json.Jackson2Tester<ExampleObject> testSet = new org.springframework.boot.test.json.Jackson2Tester<>(
88+
InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
89+
90+
}
91+
92+
}

0 commit comments

Comments
 (0)