8000 Introduce Jackson 3 support for converters · sdeleuze/spring-framework@15f7943 · GitHub
[go: up one dir, main page]

Skip to content

Commit 15f7943

Browse files
committed
Introduce Jackson 3 support for converters
This commit introduces Jackson 3 GenericHttpMessageConverter based variants of the following Jackson 2 classes (and related dependent classes). org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter -> org.springframework.http.converter.AbstractJacksonHttpMessageConverter MappingJackson2HttpMessageConverter -> JacksonJsonHttpMessageConverter MappingJackson2SmileHttpMessageConverter -> JacksonSmileHttpMessageConverter MappingJackson2CborHttpMessageConverter -> JacksonCborHttpMessageConverter MappingJackson2XmlHttpMessageConverter -> JacksonXmlHttpMessageConverter MappingJackson2YamlHttpMessageConverter -> JacksonYamlHttpMessageConverter They use hints instead of MappingJacksonValue and MappingJacksonInputMessage to support `@JsonView` and FilterProvider. Jackson 3 support is configured if found in the classpath otherwise fallback to Jackson 2. See spring-projectsgh-33798
1 parent 5c69ccc commit 15f7943

19 files changed

+3015
-79
lines changed

spring-web/src/main/java/org/springframework/http/converter/AbstractJacksonHttpMessageConverter.java

Lines changed: 509 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 61 additions & 0 deletions
< B41A tbody>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2025 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.http.converter.cbor;
18+
19+
import tools.jackson.databind.cfg.MapperBuilder;
20+
import tools.jackson.dataformat.cbor.CBORMapper;
21+
22+
import org.springframework.http.MediaType;
23+
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
24+
25+
/**
26+
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter
27+
* HttpMessageConverter} that can read and write the <a href="https://cbor.io/">CBOR</a>
28+
* data format using <a href="https://github.com/FasterXML/jackson-dataformats-binary/tree/3.x/cbor">
29+
* the dedicated Jackson 3.x extension</a>.
30+
*
31+
* <p>By default, this converter supports the {@link MediaType#APPLICATION_CBOR_VALUE}
32+
* media type. This can be overridden by setting the {@link #setSupportedMediaTypes
33+
* supportedMediaTypes} property.
34+
*
35+
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
36+
* found by {@link MapperBuilder#findModules(ClassLoader)}.
37+
*
38+
* @author Sebastien Deleuze
39+
* @since 7.0
40+
*/
41+
public class JacksonCborHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
42+
43+
/**
44+
* Construct a new instance with a {@link CBORMapper} customized with the
45+
* {@link tools.jackson.databind.JacksonModule}s found by
46+
* {@link MapperBuilder#findModules(ClassLoader)}.
47+
*/
48+
public JacksonCborHttpMessageConverter() {
49+
super(CBORMapper.builder(), MediaType.APPLICATION_CBOR);
50+
}
51+
52+
/**
53+
* Construct a new instance with the provided {@link CBORMapper}.
54+
* @see CBORMapper#builder()
55+
* @see MapperBuilder#findAndAddModules(ClassLoader)
56+
*/
57+
public JacksonCborHttpMessageConverter(CBORMapper mapper) {
58+
super(mapper, MediaType.APPLICATION_CBOR);
59+
}
60+
61+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2002-2025 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.http.converter.json;
18+
19+
import java.util.Collections;
20+
import java.util.List;
21+
22+
import org.jspecify.annotations.Nullable;
23+
import tools.jackson.core.JsonGenerator;
24+
import tools.jackson.databind.ObjectMapper;
25+
import tools.jackson.databind.cfg.MapperBuilder;
26+
import tools.jackson.databind.json.JsonMapper;
27+
28+
import org.springframework.http.MediaType;
29+
import org.springframework.http.ProblemDetail;
30+
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
31+
32+
/**
33+
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
34+
* that can read and write JSON using <a href="https://github.com/FasterXML/jackson">Jackson 3.x's</a>
35+
* {@link ObjectMapper}.
36+
*
37+
* <p>This converter can be used to bind to typed beans, or untyped
38+
* {@code HashMap} instances.
39+
*
40+
* <p>By default, this converter supports {@code application/json} and
41+
* {@code application/*+json} with {@code UTF-8} character set. This
42+
* can be overridden by setting the {@link #setSupportedMediaTypes supportedMediaTypes}
43+
* property.
44+
*
45+
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
46+
* found by {@link MapperBuilder#findModules(ClassLoader)}.
47+
*
48+
* <p>The following hints entries are supported:
49+
* <ul>
50+
* <li>A JSON view with a <code>com.fasterxml.jackson.annotation.JsonView</code>
51+
* key and the class name of the JSON view as value.</li>
52+
* <li>A filter provider with a <code>tools.jackson.databind.ser.FilterProvider</code>
53+
* key and the filter provider class name as value.</li>
54+
* </ul>
55+
*
56+
* @author Sebastien Deleuze
57+
* @since 7.0
58+
*/
59+
public class JacksonJsonHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
60+
61+
private static final List<MediaType> problemDetailMediaTypes =
62+
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
63+
64+
private static final MediaType[] DEFAULT_JSON_MIME_TYPES = new MediaType[] {
65+
MediaType.APPLICATION_JSON, new MediaType("application", "*+json") };
66+
67+
68+
private @Nullable String jsonPrefix;
69+
70+
71+
/**
72+
* Construct a new instance with a {@link JsonMapper} customized with the
73+
* {@link tools.jackson.databind.JacksonModule}s found by
74+
* {@link MapperBuilder#findModules(ClassLoader)} and
75+
* {@link ProblemDetailJacksonMixin}.
76+
*/
77+
public JacksonJsonHttpMessageConverter() {
78+
super(JsonMapper.builder().addMixIn(ProblemDetail.class, ProblemDetailJacksonMixin.class), DEFAULT_JSON_MIME_TYPES);
79+
}
80+
81+
/**
82+
* Construct a new instance with the provided {@link ObjectMapper}.
83+
* @see JsonMapper#builder()
84+
* @see MapperBuilder#findModules(ClassLoader)
85+
*/
86+
public JacksonJsonHttpMessageConverter(ObjectMapper objectMapper) {
87+
super(objectMapper, DEFAULT_JSON_MIME_TYPES);
88+
}
89+
90+
91+
/**
92+
* Specify a custom prefix to use for this view's JSON output.
93+
* Default is none.
94+
* @see #setPrefixJson
95+
*/
96+
public void setJsonPrefix(String jsonPrefix) {
97+
this.jsonPrefix = jsonPrefix;
98+
}
99+
100+
/**
101+
* Indicate whether the JSON output by this view should be prefixed with ")]}', ". Default is {@code false}.
102+
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
103+
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
104+
* This prefix should be stripped before parsing the string as JSON.
105+
* @see #setJsonPrefix
106+
*/
107+
public void setPrefixJson(boolean prefixJson) {
108+
this.jsonPrefix = (prefixJson ? ")]}', " : null);
109+
}
110+
111+
112+
@Override
113+
protected List<MediaType> getMediaTypesForProblemDetail() {
114+
return problemDetailMediaTypes;
115+
}
116+
117+
@Override
118+
protected void writePrefix(JsonGenerator generator, Object object) {
119+
if (this.jsonPrefix != null) {
120+
generator.writeRaw(this.jsonPrefix);
121+
}
122+
}
123+
124+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2002-2025 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.http.converter.smile;
18+
19+
import tools.jackson.databind.cfg.MapperBuilder;
20+
import tools.jackson.dataformat.smile.SmileMapper;
21+
22+
import org.springframework.http.MediaType;
23+
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
24+
25+
/**
26+
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
27+
* that can read and write Smile data format ("binary JSON") using
28+
* <a href="https://github.com/FasterXML/jackson-dataformats-binary/tree/3.x/smile">
29+
* the dedicated Jackson 3.x extension</a>.
30+
*
31+
* <p>By default, this converter supports {@code "application/x-jackson-smile"}
32+
* media type. This can be overridden by setting the
33+
* {@link #setSupportedMediaTypes supportedMediaTypes} property.
34+
*
35+
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
36+
* found by {@link MapperBuilder#findModules(ClassLoader)}.
37+
*
38+
* @author Sebastien Deleuze
39+
* @since 7.0
40+
*/
41+
public class JacksonSmileHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
42+
43+
private static final MediaType DEFAULT_SMILE_MIME_TYPES = new MediaType("application", "x-jackson-smile");
44+
45+
/**
46+
* Construct a new instance with a {@link SmileMapper} customized with the
47+
* {@link tools.jackson.databind.JacksonModule}s found by
48+
* {@link MapperBuilder#findModules(ClassLoader)}.
49+
*/
50+
public JacksonSmileHttpMessageConverter() {
51+
super(SmileMapper.builder(), DEFAULT_SMILE_MIME_TYPES);
52+
}
53+
54+
/**
55+
* Construct a new instance with the provided {@link SmileMapper}.
56+
* @see SmileMapper#builder()
57+
* @see MapperBuilder#findAndAddModules(ClassLoader)
58+
*/
59+
public JacksonSmileHttpMessageConverter(SmileMapper mapper) {
60+
super(mapper, DEFAULT_SMILE_MIME_TYPES);
61+
}
62+
63+
}

spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,21 @@
1717
package org.springframework.http.converter.support;
1818

1919
import org.springframework.http.converter.FormHttpMessageConverter;
20+
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
2021
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
2122
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
2223
import org.springframework.http.converter.json.GsonHttpMessageConverter;
24+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
2325
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
2426
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
2527
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
2628
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
29+
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
2730
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
31+
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
2832
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
2933
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
34+
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
3035
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
3136
import org.springframework.util.ClassUtils;
3237

@@ -44,14 +49,24 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
4449

4550
private static final boolean jaxb2Present;
4651

52+
private static final boolean jacksonPresent;
53+
4754
private static final boolean jackson2Present;
4855

56+
private static final boolean jacksonXmlPresent;
57+
4958
private static final boolean jackson2XmlPresent;
5059

60+
private static final boolean jacksonSmilePresent;
61+
5162
private static final boolean jackson2SmilePresent;
5263

64+
private static final boolean jacksonCborPresent;
65+
5366
private static final boolean jackson2CborPresent;
5467

68+
private static final boolean jacksonYamlPresent;
69+
5570
private static final boolean jackson2YamlPresent;
5671

5772
private static final boolean gsonPresent;
@@ -67,12 +82,17 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
6782
static {
6883
ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
6984
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
85+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
7086
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
7187
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
72-
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
73-
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
74-
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
75-
jackson2YamlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
88+
jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader);
89+
jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
90+
jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader);
91+
jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
92+
jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader);
93+
jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
94+
jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader);
95+
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
7696
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
7797
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
7898
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
@@ -83,11 +103,14 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
83103

84104
public AllEncompassingFormHttpMessageConverter() {
85105

86-
if (jaxb2Present && !jackson2XmlPresent) {
106+
if (jaxb2Present && !jacksonXmlPresent && !jackson2XmlPresent) {
87107
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
88108
}
89109

90-
if (jackson2Present) {
110+
if (jacksonPresent) {
111+
addPartConverter(new JacksonJsonHttpMessageConverter());
112+
}
113+
else if (jackson2Present) {
91114
addPartConverter(new MappingJackson2HttpMessageConverter());
92115
}
93116
else if (gsonPresent) {
@@ -100,22 +123,34 @@ else if (kotlinSerializationJsonPresent) {
100123
addPartConverter(new KotlinSerializationJsonHttpMessageConverter());
101124
}
102125

103-
if (jackson2XmlPresent) {
126+
if (jacksonXmlPresent) {
127+
addPartConverter(new JacksonXmlHttpMessageConverter());
128+
}
129+
else if (jackson2XmlPresent) {
104130
addPartConverter(new MappingJackson2XmlHttpMessageConverter());
105131
}
106132

107-
if (jackson2SmilePresent) {
133+
if (jacksonSmilePresent) {
134+
addPartConverter(new JacksonSmileHttpMessageConverter());
135+
}
136+
else if (jackson2SmilePresent) {
108137
addPartConverter(new MappingJackson2SmileHttpMessageConverter());
109138
}
110139

111-
if (jackson2CborPresent) {
140+
if (jacksonCborPresent) {
141+
addPartConverter(new JacksonCborHttpMessageConverter());
142+
}
143+
else if (jackson2CborPresent) {
112144
addPartConverter(new MappingJackson2CborHttpMessageConverter());
113145
}
114146
else if (kotlinSerializationCborPresent) {
115147
addPartConverter(new KotlinSerializationCborHttpMessageConverter());
116148
}
117149

118-
if (jackson2YamlPresent) {
150+
if (jacksonYamlPresent) {
151+
addPartConverter(new JacksonYamlHttpMessageConverter());
152+
}
153+
else if (jackson2YamlPresent) {
119154
addPartConverter(new MappingJackson2YamlHttpMessageConverter());
120155
}
121156

0 commit comments

Comments
 (0)
0