Introduce Jackson 3 support for converters

This commit introduces Jackson 3 SmartHttpMessageConverter 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.

JacksonHandlerInstantiator needs to be enabled explicitly if needed.

See gh-33798
This commit is contained in:
Sébastien Deleuze
2025-05-13 12:38:18 +02:00
parent 7a8db31dc3
commit d4e4a9ae06
23 changed files with 3028 additions and 86 deletions

View File

@@ -0,0 +1,509 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import com.fasterxml.jackson.annotation.JsonView;
import org.jspecify.annotations.Nullable;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonEncoding;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.PrettyPrinter;
import tools.jackson.core.util.DefaultIndenter;
import tools.jackson.core.util.DefaultPrettyPrinter;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectReader;
import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.SerializationConfig;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.ser.FilterProvider;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonInputMessage;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.TypeUtils;
/**
* Abstract base class for Jackson based and content type independent
* {@link HttpMessageConverter} implementations.
*
* <p>The following hint entries are supported:
* <ul>
* <li>A JSON view with a <code>com.fasterxml.jackson.annotation.JsonView</code>
* key and the class name of the JSON view as value.</li>
* <li>A filter provider with a <code>tools.jackson.databind.ser.FilterProvider</code>
* key and the filter provider class name as value.</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 7.0
* @see JacksonJsonHttpMessageConverter
*/
public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartHttpMessageConverter<Object> {
private static final String JSON_VIEW_HINT = JsonView.class.getName();
private static final String FILTER_PROVIDER_HINT = FilterProvider.class.getName();
private static final Map<String, JsonEncoding> ENCODINGS;
private static volatile @Nullable List<JacksonModule> modules = null;
static {
ENCODINGS = CollectionUtils.newHashMap(JsonEncoding.values().length);
for (JsonEncoding encoding : JsonEncoding.values()) {
ENCODINGS.put(encoding.getJavaName(), encoding);
}
ENCODINGS.put("US-ASCII", JsonEncoding.UTF8);
}
protected final ObjectMapper defaultObjectMapper;
private @Nullable Map<Class<?>, Map<MediaType, ObjectMapper>> objectMapperRegistrations;
private final @Nullable PrettyPrinter ssePrettyPrinter;
/**
* Construct a new instance with a provided {@link MapperBuilder builder}
* customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)}.
*/
private AbstractJacksonHttpMessageConverter(MapperBuilder<?, ?> builder) {
this.defaultObjectMapper = builder.addModules(initModules()).build();
this.ssePrettyPrinter = initSsePrettyPrinter();
}
/**
* Construct a new instance with the provided {@link MapperBuilder builder}
* customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MediaType}.
*/
protected AbstractJacksonHttpMessageConverter(MapperBuilder<?, ?> builder, MediaType supportedMediaType) {
this(builder);
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
}
/**
* Construct a new instance with the provided {@link MapperBuilder builder}
* customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MediaType}s.
*/
protected AbstractJacksonHttpMessageConverter(MapperBuilder<?, ?> builder, MediaType... supportedMediaTypes) {
this(builder);
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
/**
* Construct a new instance with a provided {@link ObjectMapper}.
*/
protected AbstractJacksonHttpMessageConverter(ObjectMapper objectMapper) {
this.defaultObjectMapper = objectMapper;
this.ssePrettyPrinter = initSsePrettyPrinter();
}
/**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MediaType}.
*/
protected AbstractJacksonHttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) {
this(objectMapper);
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
}
/**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MediaType}s.
*/
protected AbstractJacksonHttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) {
this(objectMapper);
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
private List<JacksonModule> initModules() {
if (modules == null) {
modules = MapperBuilder.findModules(AbstractJacksonHttpMessageConverter.class.getClassLoader());
}
return Objects.requireNonNull(modules);
}
private PrettyPrinter initSsePrettyPrinter() {
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
return prettyPrinter;
}
@Override
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
super.setSupportedMediaTypes(supportedMediaTypes);
}
/**
* Return the main {@code ObjectMapper} in use.
*/
public ObjectMapper getObjectMapper() {
return this.defaultObjectMapper;
}
/**
* Configure the {@link ObjectMapper} instances to use for the given
* {@link Class}. This is useful when you want to deviate from the
* {@link #getObjectMapper() default} ObjectMapper or have the
* {@code ObjectMapper} vary by {@code MediaType}.
* <p><strong>Note:</strong> Use of this method effectively turns off use of
* the default {@link #getObjectMapper() ObjectMapper} and
* {@link #setSupportedMediaTypes(List) supportedMediaTypes} for the given
* class. Therefore it is important for the mappings configured here to
* {@link MediaType#includes(MediaType) include} every MediaType that must
* be supported for the given class.
* @param clazz the type of Object to register ObjectMapper instances for
* @param registrar a consumer to populate or otherwise update the
* MediaType-to-ObjectMapper associations for the given Class
*/
public void registerObjectMappersForType(Class<?> clazz, Consumer<Map<MediaType, ObjectMapper>> registrar) {
if (this.objectMapperRegistrations == null) {
this.objectMapperRegistrations = new LinkedHashMap<>();
}
Map<MediaType, ObjectMapper> registrations =
this.objectMapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>());
registrar.accept(registrations);
}
/**
* Return ObjectMapper registrations for the given class, if any.
* @param clazz the class to look up for registrations for
* @return a map with registered MediaType-to-ObjectMapper registrations,
* or empty if in case of no registrations for the given class.
*/
public Map<MediaType, ObjectMapper> getObjectMappersForType(Class<?> clazz) {
for (Map.Entry<Class<?>, Map<MediaType, ObjectMapper>> entry : getObjectMapperRegistrations().entrySet()) {
if (entry.getKey().isAssignableFrom(clazz)) {
return entry.getValue();
}
}
return Collections.emptyMap();
}
@Override
public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
List<MediaType> result = null;
for (Map.Entry<Class<?>, Map<MediaType, ObjectMapper>> entry : getObjectMapperRegistrations().entrySet()) {
if (entry.getKey().isAssignableFrom(clazz)) {
result = (result != null ? result : new ArrayList<>(entry.getValue().size()));
result.addAll(entry.getValue().keySet());
}
}
if (!CollectionUtils.isEmpty(result)) {
return result;
}
return (ProblemDetail.class.isAssignableFrom(clazz) ?
getMediaTypesForProblemDetail() : getSupportedMediaTypes());
}
private Map<Class<?>, Map<MediaType, ObjectMapper>> getObjectMapperRegistrations() {
return (this.objectMapperRegistrations != null ? this.objectMapperRegistrations : Collections.emptyMap());
}
/**
* Return the supported media type(s) for {@link ProblemDetail}.
* By default, an empty list, unless overridden in subclasses.
*/
protected List<MediaType> getMediaTypesForProblemDetail() {
return Collections.emptyList();
}
@Override
public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) {
if (!canRead(mediaType)) {
return false;
}
Class<?> clazz = type.resolve();
if (clazz == null) {
return false;
}
return this.objectMapperRegistrations == null || selectObjectMapper(clazz, mediaType) != null;
}
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
if (!canWrite(mediaType)) {
return false;
}
if (mediaType != null && mediaType.getCharset() != null) {
Charset charset = mediaType.getCharset();
if (!ENCODINGS.containsKey(charset.name())) {
return false;
}
}
if (MappingJacksonValue.class.isAssignableFrom(clazz)) {
throw new UnsupportedOperationException("MappingJacksonValue is not supported, use hints instead");
}
return this.objectMapperRegistrations == null || selectObjectMapper(clazz, mediaType) != null;
}
/**
* Select an ObjectMapper to use, either the main ObjectMapper or another
* if the handling for the given Class has been customized through
* {@link #registerObjectMappersForType(Class, Consumer)}.
*/
private @Nullable ObjectMapper selectObjectMapper(Class<?> targetType, @Nullable MediaType targetMediaType) {
if (targetMediaType == null || CollectionUtils.isEmpty(this.objectMapperRegistrations)) {
return this.defaultObjectMapper;
}
for (Map.Entry<Class<?>, Map<MediaType, ObjectMapper>> typeEntry : getObjectMapperRegistrations().entrySet()) {
if (typeEntry.getKey().isAssignableFrom(targetType)) {
for (Map.Entry<MediaType, ObjectMapper> objectMapperEntry : typeEntry.getValue().entrySet()) {
if (objectMapperEntry.getKey().includes(targetMediaType)) {
return objectMapperEntry.getValue();
}
}
// No matching registrations
return null;
}
}
// No registrations
return this.defaultObjectMapper;
}
@Override
public Object read(ResolvableType type, HttpInputMessage inputMessage, @Nullable Map<String, Object> hints)
throws IOException, HttpMessageNotReadableException {
Class<?> contextClass = (type.getSource() instanceof MethodParameter parameter ? parameter.getContainingClass() : null);
JavaType javaType = getJavaType(type.getType(), contextClass);
return readJavaType(javaType, inputMessage, hints);
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(clazz, null);
return readJavaType(javaType, inputMessage, null);
}
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage, @Nullable Map<String, Object> hints) throws IOException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = getCharset(contentType);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
Assert.state(objectMapper != null, () -> "No ObjectMapper for " + javaType);
boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
"UTF-16".equals(charset.name()) ||
"UTF-32".equals(charset.name());
try {
InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
if (inputMessage instanceof MappingJacksonInputMessage) {
throw new UnsupportedOperationException("MappingJacksonInputMessage is not supported, use hints instead");
}
ObjectReader objectReader = objectMapper.readerFor(javaType);
if (hints != null && hints.containsKey(JSON_VIEW_HINT)) {
objectReader = objectReader.withView((Class<?>) hints.get(JSON_VIEW_HINT));
}
objectReader = customizeReader(objectReader, javaType);
if (isUnicode) {
return objectReader.readValue(inputStream);
}
else {
Reader reader = new InputStreamReader(inputStream, charset);
return objectReader.readValue(reader);
}
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JacksonException ex) {
throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
}
}
/**
* Subclasses can use this method to customize {@link ObjectReader} used
* for reading values.
* @param reader the reader instance to customize
* @param javaType the target type of element values to read to
* @return the customized {@link ObjectReader}
*/
protected ObjectReader customizeReader(ObjectReader reader, JavaType javaType) {
return reader;
}
/**
* Determine the charset to use for JSON input.
* <p>By default this is either the charset from the input {@code MediaType}
* or otherwise falling back on {@code UTF-8}. Can be overridden in subclasses.
* @param contentType the content type of the HTTP input message
* @return the charset to use
*/
protected Charset getCharset(@Nullable MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
return contentType.getCharset();
}
else {
return StandardCharsets.UTF_8;
}
}
@Override
protected void writeInternal(Object object, ResolvableType resolvableType, HttpOutputMessage outputMessage, @Nullable Map<String, Object> hints)
throws IOException, HttpMessageNotWritableException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
Class<?> clazz = object.getClass();
ObjectMapper objectMapper = selectObjectMapper(clazz, contentType);
Assert.state(objectMapper != null, () -> "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
Class<?> jsonView = null;
FilterProvider filters = null;
JavaType javaType = null;
Type type = resolvableType.getType();
if (TypeUtils.isAssignable(type, object.getClass())) {
javaType = getJavaType(type, null);
}
if (hints != null) {
jsonView = (Class<?>) hints.get(JSON_VIEW_HINT);
filters = (FilterProvider) hints.get(FILTER_PROVIDER_HINT);
}
ObjectWriter objectWriter = (jsonView != null ?
objectMapper.writerWithView(jsonView) : objectMapper.writer());
if (filters != null) {
objectWriter = objectWriter.with(filters);
}
if (javaType != null && (javaType.isContainerType() || javaType.isTypeOrSubTypeOf(Optional.class))) {
objectWriter = objectWriter.forType(javaType);
}
SerializationConfig config = objectWriter.getConfig();
if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&
config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
objectWriter = objectWriter.with(this.ssePrettyPrinter);
}
objectWriter = customizeWriter(objectWriter, javaType, contentType);
try (JsonGenerator generator = objectWriter.createGenerator(outputStream, encoding)) {
writePrefix(generator, object);
objectWriter.writeValue(generator, object);
writeSuffix(generator, object);
generator.flush();
}
catch (InvalidDefinitionException ex) {
throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
}
catch (JacksonException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
/**
* Subclasses can use this method to customize {@link ObjectWriter} used
* for writing values.
* @param writer the writer instance to customize
* @param javaType the type of element values to write
* @param contentType the selected media type
* @return the customized {@link ObjectWriter}
*/
protected ObjectWriter customizeWriter(
ObjectWriter writer, @Nullable JavaType javaType, @Nullable MediaType contentType) {
return writer;
}
/**
* Write a prefix before the main content.
* @param generator the generator to use for writing content.
* @param object the object to write to the output message.
*/
protected void writePrefix(JsonGenerator generator, Object object) {
}
/**
* Write a suffix after the main content.
* @param generator the generator to use for writing content.
* @param object the object to write to the output message.
*/
protected void writeSuffix(JsonGenerator generator, Object object) {
}
/**
* Return the Jackson {@link JavaType} for the specified type and context class.
* @param type the generic type to return the Jackson JavaType for
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature (can be {@code null})
* @return the Jackson JavaType
*/
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
return this.defaultObjectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass));
}
/**
* Determine the JSON encoding to use for the given content type.
* @param contentType the media type as requested by the caller
* @return the JSON encoding to use (never {@code null})
*/
protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
Charset charset = contentType.getCharset();
JsonEncoding encoding = ENCODINGS.get(charset.name());
if (encoding != null) {
return encoding;
}
}
return JsonEncoding.UTF8;
}
@Override
protected boolean supportsRepeatableWrites(Object o) {
return true;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter.cbor;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.dataformat.cbor.CBORMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter
* HttpMessageConverter} that can read and write the <a href="https://cbor.io/">CBOR</a>
* data format using <a href="https://github.com/FasterXML/jackson-dataformats-binary/tree/3.x/cbor">
* the dedicated Jackson 3.x extension</a>.
*
* <p>By default, this converter supports the {@link MediaType#APPLICATION_CBOR_VALUE}
* media type. This can be overridden by setting the {@link #setSupportedMediaTypes
* supportedMediaTypes} property.
*
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
* found by {@link MapperBuilder#findModules(ClassLoader)}.
*
* @author Sebastien Deleuze
* @since 7.0
*/
public class JacksonCborHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
/**
* Construct a new instance with a {@link CBORMapper} customized with the
* {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)}.
*/
public JacksonCborHttpMessageConverter() {
super(CBORMapper.builder(), MediaType.APPLICATION_CBOR);
}
/**
* Construct a new instance with the provided {@link CBORMapper}.
* @see CBORMapper#builder()
* @see MapperBuilder#findAndAddModules(ClassLoader)
*/
public JacksonCborHttpMessageConverter(CBORMapper mapper) {
super(mapper, MediaType.APPLICATION_CBOR);
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter.json;
import java.util.Collections;
import java.util.List;
import org.jspecify.annotations.Nullable;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
* that can read and write JSON using <a href="https://github.com/FasterXML/jackson">Jackson 3.x's</a>
* {@link ObjectMapper}.
*
* <p>This converter can be used to bind to typed beans, or untyped
* {@code HashMap} instances.
*
* <p>By default, this converter supports {@code application/json} and
* {@code application/*+json} with {@code UTF-8} character set. This
* can be overridden by setting the {@link #setSupportedMediaTypes supportedMediaTypes}
* property.
*
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
* found by {@link MapperBuilder#findModules(ClassLoader)}.
*
* <p>The following hints entries are supported:
* <ul>
* <li>A JSON view with a <code>com.fasterxml.jackson.annotation.JsonView</code>
* key and the class name of the JSON view as value.</li>
* <li>A filter provider with a <code>tools.jackson.databind.ser.FilterProvider</code>
* key and the filter provider class name as value.</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 7.0
*/
public class JacksonJsonHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
private static final List<MediaType> problemDetailMediaTypes =
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
private static final MediaType[] DEFAULT_JSON_MIME_TYPES = new MediaType[] {
MediaType.APPLICATION_JSON, new MediaType("application", "*+json") };
private @Nullable String jsonPrefix;
/**
* Construct a new instance with a {@link JsonMapper} customized with the
* {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)} and
* {@link ProblemDetailJacksonMixin}.
*/
public JacksonJsonHttpMessageConverter() {
super(JsonMapper.builder().addMixIn(ProblemDetail.class, ProblemDetailJacksonMixin.class), DEFAULT_JSON_MIME_TYPES);
}
/**
* Construct a new instance with the provided {@link ObjectMapper}.
* @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader)
*/
public JacksonJsonHttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, DEFAULT_JSON_MIME_TYPES);
}
/**
* Specify a custom prefix to use for this view's JSON output.
* Default is none.
* @see #setPrefixJson
*/
public void setJsonPrefix(String jsonPrefix) {
this.jsonPrefix = jsonPrefix;
}
/**
* Indicate whether the JSON output by this view should be prefixed with ")]}', ". Default is {@code false}.
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
* This prefix should be stripped before parsing the string as JSON.
* @see #setJsonPrefix
*/
public void setPrefixJson(boolean prefixJson) {
this.jsonPrefix = (prefixJson ? ")]}', " : null);
}
@Override
protected List<MediaType> getMediaTypesForProblemDetail() {
return problemDetailMediaTypes;
}
@Override
protected void writePrefix(JsonGenerator generator, Object object) {
if (this.jsonPrefix != null) {
generator.writeRaw(this.jsonPrefix);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter.smile;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.dataformat.smile.SmileMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
* that can read and write Smile data format ("binary JSON") using
* <a href="https://github.com/FasterXML/jackson-dataformats-binary/tree/3.x/smile">
* the dedicated Jackson 3.x extension</a>.
*
* <p>By default, this converter supports {@code "application/x-jackson-smile"}
* media type. This can be overridden by setting the
* {@link #setSupportedMediaTypes supportedMediaTypes} property.
*
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
* found by {@link MapperBuilder#findModules(ClassLoader)}.
*
* @author Sebastien Deleuze
* @since 7.0
*/
public class JacksonSmileHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
private static final MediaType DEFAULT_SMILE_MIME_TYPES = new MediaType("application", "x-jackson-smile");
/**
* Construct a new instance with a {@link SmileMapper} customized with the
* {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)}.
*/
public JacksonSmileHttpMessageConverter() {
super(SmileMapper.builder(), DEFAULT_SMILE_MIME_TYPES);
}
/**
* Construct a new instance with the provided {@link SmileMapper}.
* @see SmileMapper#builder()
* @see MapperBuilder#findAndAddModules(ClassLoader)
*/
public JacksonSmileHttpMessageConverter(SmileMapper mapper) {
super(mapper, DEFAULT_SMILE_MIME_TYPES);
}
}

View File

@@ -17,16 +17,21 @@
package org.springframework.http.converter.support;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
import org.springframework.util.ClassUtils;
@@ -44,14 +49,24 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
private static final boolean jaxb2Present;
private static final boolean jacksonPresent;
private static final boolean jackson2Present;
private static final boolean jacksonXmlPresent;
private static final boolean jackson2XmlPresent;
private static final boolean jacksonSmilePresent;
private static final boolean jackson2SmilePresent;
private static final boolean jacksonCborPresent;
private static final boolean jackson2CborPresent;
private static final boolean jacksonYamlPresent;
private static final boolean jackson2YamlPresent;
private static final boolean gsonPresent;
@@ -67,12 +82,17 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
static {
ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
jackson2YamlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader);
jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader);
jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader);
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
@@ -83,11 +103,14 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
public AllEncompassingFormHttpMessageConverter() {
if (jaxb2Present && !jackson2XmlPresent) {
if (jaxb2Present && !jacksonXmlPresent && !jackson2XmlPresent) {
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
if (jacksonPresent) {
addPartConverter(new JacksonJsonHttpMessageConverter());
}
else if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
@@ -100,22 +123,34 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
addPartConverter(new KotlinSerializationJsonHttpMessageConverter());
}
if (jackson2XmlPresent) {
if (jacksonXmlPresent) {
addPartConverter(new JacksonXmlHttpMessageConverter());
}
else if (jackson2XmlPresent) {
addPartConverter(new MappingJackson2XmlHttpMessageConverter());
}
if (jackson2SmilePresent) {
if (jacksonSmilePresent) {
addPartConverter(new JacksonSmileHttpMessageConverter());
}
else if (jackson2SmilePresent) {
addPartConverter(new MappingJackson2SmileHttpMessageConverter());
}
if (jackson2CborPresent) {
if (jacksonCborPresent) {
addPartConverter(new JacksonCborHttpMessageConverter());
}
else if (jackson2CborPresent) {
addPartConverter(new MappingJackson2CborHttpMessageConverter());
}
else if (kotlinSerializationCborPresent) {
addPartConverter(new KotlinSerializationCborHttpMessageConverter());
}
if (jackson2YamlPresent) {
if (jacksonYamlPresent) {
addPartConverter(new JacksonYamlHttpMessageConverter());
}
else if (jackson2YamlPresent) {
addPartConverter(new MappingJackson2YamlHttpMessageConverter());
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter.xml;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.dataformat.xml.XmlFactory;
import tools.jackson.dataformat.xml.XmlMapper;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
import org.springframework.http.converter.json.ProblemDetailJacksonXmlMixin;
import org.springframework.util.xml.StaxUtils;
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
* that can read and write XML using <a href="https://github.com/FasterXML/jackson-dataformat-xml">
* Jackson 3.x extension component for reading and writing XML encoded data</a>.
*
* <p>By default, this converter supports {@code application/xml}, {@code text/xml}, and
* {@code application/*+xml} with {@code UTF-8} character set. This can be overridden by
* setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
*
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
* found by {@link MapperBuilder#findModules(ClassLoader)}.
*
* <p>The following hint entries are supported:
* <ul>
* <li>A JSON view with a <code>com.fasterxml.jackson.annotation.JsonView</code>
* key and the class name of the JSON view as value.</li>
* <li>A filter provider with a <code>tools.jackson.databind.ser.FilterProvider</code>
* key and the filter provider class name as value.</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 7.0
*/
public class JacksonXmlHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
private static final List<MediaType> problemDetailMediaTypes =
Collections.singletonList(MediaType.APPLICATION_PROBLEM_XML);
private static final MediaType[] DEFAULT_XML_MIME_TYPES = new MediaType[] {
new MediaType("application", "xml", StandardCharsets.UTF_8),
new MediaType("text", "xml", StandardCharsets.UTF_8),
new MediaType("application", "*+xml", StandardCharsets.UTF_8)
};
/**
* Construct a new instance with a {@link XmlMapper} created from
* {@link #defensiveXmlFactory} and customized with the
* {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)} and
* {@link ProblemDetailJacksonXmlMixin}.
*/
public JacksonXmlHttpMessageConverter() {
this(XmlMapper.builder(defensiveXmlFactory()));
}
/**
* Construct a new instance with the provided {@link XmlMapper.Builder builder}
* customized with the {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)} and
* {@link ProblemDetailJacksonXmlMixin}.
*/
public JacksonXmlHttpMessageConverter(XmlMapper.Builder builder) {
super(builder.addMixIn(ProblemDetail.class, ProblemDetailJacksonXmlMixin.class), DEFAULT_XML_MIME_TYPES);
}
/**
* Construct a new instance with the provided {@link XmlMapper}.
* @see XmlMapper#builder()
* @see MapperBuilder#findModules(ClassLoader)
*/
public JacksonXmlHttpMessageConverter(XmlMapper xmlMapper) {
super(xmlMapper, DEFAULT_XML_MIME_TYPES);
}
/**
* Return an {@link XmlFactory} created from {@link StaxUtils#createDefensiveInputFactory}
* with Spring's defensive setup, i.e. no support for the resolution of DTDs and external
* entities.
*/
public static XmlFactory defensiveXmlFactory() {
return new XmlFactory(StaxUtils.createDefensiveInputFactory());
}
@Override
protected List<MediaType> getMediaTypesForProblemDetail() {
return problemDetailMediaTypes;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2025 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter.yaml;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.dataformat.yaml.YAMLMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter
* HttpMessageConverter} that can read and write the <a href="https://yaml.io/">YAML</a>
* data format using <a href="https://github.com/FasterXML/jackson-dataformats-text/tree/3.x/yaml">
* the dedicated Jackson 3.x extension</a>.
*
* <p>By default, this converter supports the {@link MediaType#APPLICATION_YAML_VALUE}
* media type. This can be overridden by setting the {@link #setSupportedMediaTypes
* supportedMediaTypes} property.
*
* <p>The default constructor loads {@link tools.jackson.databind.JacksonModule}s
* found by {@link MapperBuilder#findModules(ClassLoader)}.
*
* @author Sebastien Deleuze
* @since 7.0
*/
public class JacksonYamlHttpMessageConverter extends AbstractJacksonHttpMessageConverter {
/**
* Construct a new instance with a {@link YAMLMapper} customized with the
* {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)}.
*/
public JacksonYamlHttpMessageConverter() {
super(YAMLMapper.builder(), MediaType.APPLICATION_YAML);
}
/**
* Construct a new instance with the provided {@link YAMLMapper}.
* @see YAMLMapper#builder()
* @see MapperBuilder#findAndAddModules(ClassLoader)
*/
public JacksonYamlHttpMessageConverter(YAMLMapper mapper) {
super(mapper, MediaType.APPLICATION_YAML);
}
}

View File

@@ -47,19 +47,24 @@ import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -96,14 +101,24 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
private static final boolean jaxb2Present;
private static final boolean jacksonPresent;
private static final boolean jackson2Present;
private static final boolean jacksonXmlPresent;
private static final boolean jackson2XmlPresent;
private static final boolean jacksonSmilePresent;
private static final boolean jackson2SmilePresent;
private static final boolean jacksonCborPresent;
private static final boolean jackson2CborPresent;
private static final boolean jacksonYamlPresent;
private static final boolean jackson2YamlPresent;
private static final boolean gsonPresent;
@@ -126,12 +141,17 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", loader);
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", loader);
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", loader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", loader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", loader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", loader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", loader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", loader);
jackson2YamlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", loader);
jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", loader);
jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", loader);
jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", loader);
jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", loader);
jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", loader);
jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", loader);
jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", loader);
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", loader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", loader);
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", loader);
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", loader);
@@ -463,7 +483,10 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
if (jacksonXmlPresent) {
this.messageConverters.add(new JacksonXmlHttpMessageConverter());
}
else if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
@@ -474,7 +497,10 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
this.messageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
}
if (jackson2Present) {
if (jacksonPresent) {
this.messageConverters.add(new JacksonJsonHttpMessageConverter());
}
else if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
@@ -487,18 +513,27 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
this.messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
}
if (jacksonSmilePresent) {
this.messageConverters.add(new JacksonSmileHttpMessageConverter());
}
if (jackson2SmilePresent) {
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
if (jackson2CborPresent) {
if (jacksonCborPresent) {
this.messageConverters.add(new JacksonCborHttpMessageConverter());
}
else if (jackson2CborPresent) {
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
}
else if (kotlinSerializationCborPresent) {
this.messageConverters.add(new KotlinSerializationCborHttpMessageConverter());
}
if (jackson2YamlPresent) {
if (jacksonYamlPresent) {
this.messageConverters.add(new JacksonYamlHttpMessageConverter());
}
else if (jackson2YamlPresent) {
this.messageConverters.add(new MappingJackson2YamlHttpMessageConverter());
}
}

View File

@@ -56,19 +56,24 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.SmartHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -124,14 +129,24 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
private static final boolean jaxb2Present;
private static final boolean jacksonPresent;
private static final boolean jackson2Present;
private static final boolean jacksonXmlPresent;
private static final boolean jackson2XmlPresent;
private static final boolean jacksonSmilePresent;
private static final boolean jackson2SmilePresent;
private static final boolean jacksonCborPresent;
private static final boolean jackson2CborPresent;
private static final boolean jacksonYamlPresent;
private static final boolean jackson2YamlPresent;
private static final boolean gsonPresent;
@@ -151,12 +166,17 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
jackson2YamlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader);
jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader);
jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader);
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
@@ -193,7 +213,10 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
if (jacksonXmlPresent) {
this.messageConverters.add(new JacksonXmlHttpMessageConverter());
}
else if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
@@ -204,7 +227,10 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
this.messageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
}
if (jackson2Present) {
if (jacksonPresent) {
this.messageConverters.add(new JacksonJsonHttpMessageConverter());
}
else if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
@@ -217,18 +243,27 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
this.messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
}
if (jackson2SmilePresent) {
if (jacksonSmilePresent) {
this.messageConverters.add(new JacksonSmileHttpMessageConverter());
}
else if (jackson2SmilePresent) {
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
if (jackson2CborPresent) {
if (jacksonCborPresent) {
this.messageConverters.add(new JacksonCborHttpMessageConverter());
}
else if (jackson2CborPresent) {
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
}
else if (kotlinSerializationCborPresent) {
this.messageConverters.add(new KotlinSerializationCborHttpMessageConverter());
}
if (jackson2YamlPresent) {
if (jacksonYamlPresent) {
this.messageConverters.add(new JacksonYamlHttpMessageConverter());
}
else if (jackson2YamlPresent) {
this.messageConverters.add(new MappingJackson2YamlHttpMessageConverter());
}