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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.cbor.CBORMapper;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x CBOR converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonCborHttpMessageConverterTests {
|
||||
|
||||
private final JacksonCborHttpMessageConverter converter = new JacksonCborHttpMessageConverter();
|
||||
private final CBORMapper mapper = CBORMapper.builder().build();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_CBOR)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, MediaType.APPLICATION_CBOR)).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_CBOR);
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body));
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_CBOR);
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,843 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFilter;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import tools.jackson.databind.JavaType;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ObjectReader;
|
||||
import tools.jackson.databind.ObjectWriter;
|
||||
import tools.jackson.databind.SerializationFeature;
|
||||
import tools.jackson.databind.cfg.EnumFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.ser.FilterProvider;
|
||||
import tools.jackson.databind.ser.std.SimpleBeanPropertyFilter;
|
||||
import tools.jackson.databind.ser.std.SimpleFilterProvider;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonJsonHttpMessageConverterTests {
|
||||
|
||||
private JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isTrue();
|
||||
assertThat(converter.canRead(Map.class, new MediaType("application", "json"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canReadWithObjectMapperRegistrationForType() {
|
||||
MediaType halJsonMediaType = MediaType.parseMediaType("application/hal+json");
|
||||
MediaType halFormsJsonMediaType = MediaType.parseMediaType("application/prs.hal-forms+json");
|
||||
|
||||
assertThat(converter.canRead(MyBean.class, halJsonMediaType)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, halFormsJsonMediaType)).isTrue();
|
||||
assertThat(converter.canRead(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
|
||||
converter.registerObjectMappersForType(MyBean.class, map -> {
|
||||
map.put(halJsonMediaType, new ObjectMapper());
|
||||
map.put(MediaType.APPLICATION_JSON, new ObjectMapper());
|
||||
});
|
||||
|
||||
assertThat(converter.canRead(MyBean.class, halJsonMediaType)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canRead(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canWrite(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse();
|
||||
assertThatThrownBy(() -> converter.canWrite(MappingJacksonValue.class, MediaType.APPLICATION_JSON)).isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test // SPR-7905
|
||||
void canReadAndWriteMicroformats() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "vnd.test-micro-type+json"))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSupportedMediaTypes() {
|
||||
MediaType[] defaultMediaTypes = {MediaType.APPLICATION_JSON, MediaType.parseMediaType("application/*+json")};
|
||||
assertThat(converter.getSupportedMediaTypes()).containsExactly(defaultMediaTypes);
|
||||
assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(defaultMediaTypes);
|
||||
|
||||
MediaType halJson = MediaType.parseMediaType("application/hal+json");
|
||||
converter.registerObjectMappersForType(MyBean.class, map -> {
|
||||
map.put(halJson, new ObjectMapper());
|
||||
map.put(MediaType.APPLICATION_JSON, new ObjectMapper());
|
||||
});
|
||||
|
||||
assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(halJson, MediaType.APPLICATION_JSON);
|
||||
assertThat(converter.getSupportedMediaTypes(Map.class)).containsExactly(defaultMediaTypes);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readTyped() throws IOException {
|
||||
String body = "{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readUntyped() throws IOException {
|
||||
String body = "{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) converter.read(HashMap.class, inputMessage);
|
||||
assertThat(result).containsEntry("string", "Foo");
|
||||
assertThat(result).containsEntry("number", 42);
|
||||
assertThat((Double) result.get("fraction")).isCloseTo(42D, within(0D));
|
||||
List<String> array = new ArrayList<>();
|
||||
array.add("Foo");
|
||||
array.add("Bar");
|
||||
assertThat(result).containsEntry("array", array);
|
||||
assertThat(result).containsEntry("bool", Boolean.TRUE);
|
||||
assertThat(result).containsEntry("bytes", "AQI=");
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[] {"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[] {0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
assertThat(result).contains("fraction\":42.0");
|
||||
assertThat(result).contains("\"array\":[\"Foo\",\"Bar\"]");
|
||||
assertThat(result).contains("\"bool\":true");
|
||||
assertThat(result).contains("\"bytes\":\"AQI=\"");
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeWithBaseType() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[] {"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[] {0x1, 0x2});
|
||||
converter.write(body, ResolvableType.forClass(MyBase.class), null, outputMessage, null);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
assertThat(result).contains("fraction\":42.0");
|
||||
assertThat(result).contains("\"array\":[\"Foo\",\"Bar\"]");
|
||||
assertThat(result).contains("\"bool\":true");
|
||||
assertThat(result).contains("\"bytes\":\"AQI=\"");
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeUTF16() throws IOException {
|
||||
MediaType contentType = new MediaType("application", "json", StandardCharsets.UTF_16BE);
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
String body = "H\u00e9llo W\u00f6rld";
|
||||
converter.write(body, contentType, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_16BE)).as("Invalid result").isEqualTo(("\"" + body + "\""));
|
||||
assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readInvalidJson() {
|
||||
String body = "FooBar";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readValidJsonWithUnknownProperty() throws IOException {
|
||||
String body = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
// Assert no HttpMessageNotReadableException is thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readAndWriteGenerics() throws Exception {
|
||||
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter() {
|
||||
@Override
|
||||
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
|
||||
if (type instanceof Class && List.class.isAssignableFrom((Class<?>)type)) {
|
||||
return new ObjectMapper().getTypeFactory().constructCollectionType(ArrayList.class, MyBean.class);
|
||||
}
|
||||
else {
|
||||
return super.getJavaType(type, contextClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
String body = "[{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
List<MyBean> results = (List<MyBean>) converter.read(List.class, inputMessage);
|
||||
assertThat(results).hasSize(1);
|
||||
MyBean result = results.get(0);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(results, MediaType.APPLICATION_JSON, outputMessage);
|
||||
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readAndWriteParameterizedType() throws Exception {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<>() {};
|
||||
|
||||
String body = "[{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(ResolvableType.forType(beansList), inputMessage, null);
|
||||
assertThat(results).hasSize(1);
|
||||
MyBean result = results.get(0);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(results, ResolvableType.forType(beansList), MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void writeParameterizedBaseType() throws Exception {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<>() {};
|
||||
ParameterizedTypeReference<List<MyBase>> baseList = new ParameterizedTypeReference<>() {};
|
||||
|
||||
String body = "[{" +
|
||||
"\"bytes\":\"AQI=\"," +
|
||||
"\"array\":[\"Foo\",\"Bar\"]," +
|
||||
"\"number\":42," +
|
||||
"\"string\":\"Foo\"," +
|
||||
"\"bool\":true," +
|
||||
"\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), null, inputMessage);
|
||||
assertThat(results).hasSize(1);
|
||||
MyBean result = results.get(0);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[] {"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[] {0x1, 0x2});
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(results, baseList.getType(), MediaType.APPLICATION_JSON, outputMessage);
|
||||
JSONAssert.assertEquals(body, outputMessage.getBodyAsString(StandardCharsets.UTF_8), true);
|
||||
}
|
||||
|
||||
// gh-24498
|
||||
@Test
|
||||
void writeOptional() throws IOException {
|
||||
ParameterizedTypeReference<Optional<MyParent>> optionalParent = new ParameterizedTypeReference<>() {};
|
||||
Optional<MyParent> result = Optional.of(new Impl1());
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
converter.write(result, ResolvableType.forType(optionalParent.getType()),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8))
|
||||
.contains("@type");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prettyPrint() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
PrettyPrintBean bean = new PrettyPrintBean();
|
||||
bean.setName("Jason");
|
||||
|
||||
ObjectMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||
this.converter = new JacksonJsonHttpMessageConverter(mapper);
|
||||
this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(result).isEqualToNormalizingNewlines("""
|
||||
{
|
||||
\s "name" : "Jason"
|
||||
}""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prettyPrintWithSse() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
outputMessage.getHeaders().setContentType(MediaType.TEXT_EVENT_STREAM);
|
||||
PrettyPrintBean bean = new PrettyPrintBean();
|
||||
bean.setName("Jason");
|
||||
|
||||
ObjectMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||
this.converter = new JacksonJsonHttpMessageConverter(mapper);
|
||||
this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(result).isEqualTo("{\ndata: \"name\" : \"Jason\"\ndata:}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixJson() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
this.converter.setPrefixJson(true);
|
||||
this.converter.write("foo", ResolvableType.forType(String.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, null);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEqualTo(")]}', \"foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixJsonCustom() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
this.converter.setJsonPrefix(")))");
|
||||
this.converter.write("foo", ResolvableType.forType(String.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, null);
|
||||
|
||||
assertThat(outputMessage.getBodyAsString(StandardCharsets.UTF_8)).isEqualTo(")))\"foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldLevelJsonView() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
|
||||
Map<String, Object> hints = Collections.singletonMap(JsonView.class.getName(), MyJacksonView1.class);
|
||||
this.converter.write(bean, ResolvableType.forType(JacksonViewBean.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"withView1\":\"with\"");
|
||||
assertThat(result).doesNotContain("\"withView2\":\"with\"");
|
||||
assertThat(result).doesNotContain("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void classLevelJsonView() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
|
||||
Map<String, Object> hints = Collections.singletonMap(JsonView.class.getName(), MyJacksonView3.class);
|
||||
this.converter.write(bean, ResolvableType.forType(JacksonViewBean.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).doesNotContain("\"withView1\":\"with\"");
|
||||
assertThat(result).doesNotContain("\"withView2\":\"with\"");
|
||||
assertThat(result).contains("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void filters() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonFilteredBean bean = new JacksonFilteredBean();
|
||||
bean.setProperty1("value");
|
||||
bean.setProperty2("value");
|
||||
|
||||
|
||||
FilterProvider filters = new SimpleFilterProvider().addFilter("myJacksonFilter",
|
||||
SimpleBeanPropertyFilter.serializeAllExcept("property2"));
|
||||
Map<String, Object> hints = Collections.singletonMap(FilterProvider.class.getName(), filters);
|
||||
this.converter.write(bean, ResolvableType.forType(JacksonFilteredBean.class), MediaType.APPLICATION_JSON,
|
||||
outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"property1\":\"value\"");
|
||||
assertThat(result).doesNotContain("\"property2\":\"value\"");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
void writeSubType() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean bean = new MyBean();
|
||||
bean.setString("Foo");
|
||||
bean.setNumber(42);
|
||||
|
||||
this.converter.write(bean, ResolvableType.forType(MyInterface.class),
|
||||
MediaType.APPLICATION_JSON, outputMessage, null);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
void writeSubTypeList() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
List<MyBean> beans = new ArrayList<>();
|
||||
MyBean foo = new MyBean();
|
||||
foo.setString("Foo");
|
||||
foo.setNumber(42);
|
||||
beans.add(foo);
|
||||
MyBean bar = new MyBean();
|
||||
bar.setString("Bar");
|
||||
bar.setNumber(123);
|
||||
beans.add(bar);
|
||||
ParameterizedTypeReference<List<MyInterface>> typeReference =
|
||||
new ParameterizedTypeReference<>() {};
|
||||
|
||||
this.converter.write(beans, ResolvableType.forType(typeReference), MediaType.APPLICATION_JSON,
|
||||
outputMessage, null);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
assertThat(result).contains("\"number\":42");
|
||||
assertThat(result).contains("\"string\":\"Bar\"");
|
||||
assertThat(result).contains("\"number\":123");
|
||||
}
|
||||
|
||||
@Test // gh-27511
|
||||
void readWithNoDefaultConstructor() throws Exception {
|
||||
String body = "{\"property1\":\"foo\",\"property2\":\"bar\"}";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
BeanWithNoDefaultConstructor bean =
|
||||
(BeanWithNoDefaultConstructor)converter.read(BeanWithNoDefaultConstructor.class, inputMessage);
|
||||
assertThat(bean.property1).isEqualTo("foo");
|
||||
assertThat(bean.property2).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readNonUnicode() throws Exception {
|
||||
String body = "{\"føø\":\"bår\"}";
|
||||
Charset charset = StandardCharsets.ISO_8859_1;
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json", charset));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) this.converter.read(HashMap.class, inputMessage);
|
||||
|
||||
assertThat(result).containsExactly(entry("føø", "bår"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void readAscii() throws Exception {
|
||||
String body = "{\"foo\":\"bar\"}";
|
||||
Charset charset = StandardCharsets.US_ASCII;
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json", charset));
|
||||
HashMap<String, Object> result = (HashMap<String, Object>) this.converter.read(HashMap.class, inputMessage);
|
||||
|
||||
assertThat(result).containsExactly(entry("foo", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeAscii() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
Map<String,Object> body = new HashMap<>();
|
||||
body.put("foo", "bar");
|
||||
Charset charset = StandardCharsets.US_ASCII;
|
||||
MediaType contentType = new MediaType("application", "json", charset);
|
||||
converter.write(body, contentType, outputMessage);
|
||||
|
||||
String result = outputMessage.getBodyAsString(charset);
|
||||
assertThat(result).isEqualTo("{\"foo\":\"bar\"}");
|
||||
assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(contentType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithCustomized() throws IOException {
|
||||
JacksonJsonHttpMessageConverterWithCustomization customizedConverter =
|
||||
new JacksonJsonHttpMessageConverterWithCustomization();
|
||||
String body = "{\"property\":\"Value1\"}";
|
||||
MockHttpInputMessage inputMessage1 = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
MockHttpInputMessage inputMessage2 = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage1.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
inputMessage2.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> customizedConverter.read(MyCustomizedBean.class, inputMessage1));
|
||||
|
||||
MyCustomizedBean customizedResult = (MyCustomizedBean) converter.read(MyCustomizedBean.class, inputMessage2);
|
||||
assertThat(customizedResult.getProperty()).isEqualTo(MyCustomEnum.VAL1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeWithCustomized() throws IOException {
|
||||
JacksonJsonHttpMessageConverterWithCustomization customizedConverter =
|
||||
new JacksonJsonHttpMessageConverterWithCustomization();
|
||||
MockHttpOutputMessage outputMessage1 = new MockHttpOutputMessage();
|
||||
MockHttpOutputMessage outputMessage2 = new MockHttpOutputMessage();
|
||||
MyCustomizedBean body = new MyCustomizedBean();
|
||||
body.setProperty(MyCustomEnum.VAL2);
|
||||
converter.write(body, null, outputMessage1);
|
||||
customizedConverter.write(body, null, outputMessage2);
|
||||
String result1 = outputMessage1.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result1).contains("\"property\":\"Value2\"");
|
||||
String result2 = outputMessage2.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result2).contains("\"property\":\"VAL2\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void repeatableWrites() throws IOException {
|
||||
MockHttpOutputMessage outputMessage1 = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
converter.write(body, null, outputMessage1);
|
||||
String result = outputMessage1.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
|
||||
MockHttpOutputMessage outputMessage2 = new MockHttpOutputMessage();
|
||||
converter.write(body, null, outputMessage2);
|
||||
result = outputMessage2.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("\"string\":\"Foo\"");
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface MyInterface {
|
||||
|
||||
String getString();
|
||||
|
||||
void setString(String string);
|
||||
}
|
||||
|
||||
|
||||
public static class MyBase implements MyInterface {
|
||||
|
||||
private String string;
|
||||
|
||||
@Override
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean extends MyBase {
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PrettyPrintBean {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private interface MyJacksonView1 {}
|
||||
|
||||
private interface MyJacksonView2 {}
|
||||
|
||||
private interface MyJacksonView3 {}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@JsonView(MyJacksonView3.class)
|
||||
private static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonFilter("myJacksonFilter")
|
||||
@SuppressWarnings("unused")
|
||||
private static class JacksonFilteredBean {
|
||||
|
||||
private String property1;
|
||||
|
||||
private String property2;
|
||||
|
||||
public String getProperty1() {
|
||||
return property1;
|
||||
}
|
||||
|
||||
public void setProperty1(String property1) {
|
||||
this.property1 = property1;
|
||||
}
|
||||
|
||||
public String getProperty2() {
|
||||
return property2;
|
||||
}
|
||||
|
||||
public void setProperty2(String property2) {
|
||||
this.property2 = property2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class BeanWithNoDefaultConstructor {
|
||||
|
||||
private final String property1;
|
||||
|
||||
private final String property2;
|
||||
|
||||
public BeanWithNoDefaultConstructor(String property1, String property2) {
|
||||
this.property1 = property1;
|
||||
this.property2 = property2;
|
||||
}
|
||||
|
||||
public String getProperty1() {
|
||||
return property1;
|
||||
}
|
||||
|
||||
public String getProperty2() {
|
||||
return property2;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyCustomizedBean {
|
||||
|
||||
private MyCustomEnum property;
|
||||
|
||||
public MyCustomEnum getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public void setProperty(MyCustomEnum property) {
|
||||
this.property = property;
|
||||
}
|
||||
}
|
||||
|
||||
public enum MyCustomEnum {
|
||||
VAL1,
|
||||
VAL2;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this == VAL1 ? "Value1" : "Value2";
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
|
||||
@JsonSubTypes(value = {@JsonSubTypes.Type(value = Impl1.class),
|
||||
@JsonSubTypes.Type(value = Impl2.class)})
|
||||
public interface MyParent {
|
||||
}
|
||||
|
||||
public static class Impl1 implements MyParent {
|
||||
}
|
||||
|
||||
public static class Impl2 implements MyParent {
|
||||
}
|
||||
|
||||
private static class JacksonJsonHttpMessageConverterWithCustomization extends JacksonJsonHttpMessageConverter {
|
||||
|
||||
@Override
|
||||
protected ObjectReader customizeReader(ObjectReader reader, JavaType javaType) {
|
||||
return reader.without(EnumFeature.READ_ENUMS_USING_TO_STRING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectWriter customizeWriter(ObjectWriter writer, @Nullable JavaType javaType, @Nullable MediaType contentType) {
|
||||
return writer.without(EnumFeature.WRITE_ENUMS_USING_TO_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.smile.SmileMapper;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x Smile converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonSmileHttpMessageConverterTests {
|
||||
|
||||
private final JacksonSmileHttpMessageConverter converter = new JacksonSmileHttpMessageConverter();
|
||||
private final SmileMapper mapper = SmileMapper.builder().build();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "x-jackson-smile"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "x-jackson-smile"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "x-jackson-smile"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body));
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(new MediaType("application", "x-jackson-smile"));
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x XML converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonXmlHttpMessageConverterTests {
|
||||
|
||||
private final JacksonXmlHttpMessageConverter converter = new JacksonXmlHttpMessageConverter();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("text", "xml"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "soap+xml"))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("text", "xml", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("text", "xml", StandardCharsets.ISO_8859_1))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("text", "xml"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "soap+xml"))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("text", "xml", StandardCharsets.UTF_8))).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("text", "xml", StandardCharsets.ISO_8859_1))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
String body = "<MyBean>" +
|
||||
"<string>Foo</string>" +
|
||||
"<number>42</number>" +
|
||||
"<fraction>42.0</fraction>" +
|
||||
"<array><array>Foo</array>" +
|
||||
"<array>Bar</array></array>" +
|
||||
"<bool>true</bool>" +
|
||||
"<bytes>AQI=</bytes></MyBean>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("<string>Foo</string>");
|
||||
assertThat(result).contains("<number>42</number>");
|
||||
assertThat(result).contains("<fraction>42.0</fraction>");
|
||||
assertThat(result).contains("<array><array>Foo</array><array>Bar</array></array>");
|
||||
assertThat(result).contains("<bool>true</bool>");
|
||||
assertThat(result).contains("<bytes>AQI=</bytes>");
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(new MediaType("application", "xml", StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readInvalidXml() {
|
||||
String body = "FooBar";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() ->
|
||||
converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readValidXmlWithUnknownProperty() throws IOException {
|
||||
String body = "<MyBean><string>string</string><unknownProperty>value</unknownProperty></MyBean>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
converter.read(MyBean.class, inputMessage);
|
||||
// Assert no HttpMessageNotReadableException is thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonView() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
JacksonViewBean bean = new JacksonViewBean();
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
|
||||
Map<String, Object> hints = Collections.singletonMap(JsonView.class.getName(), MyJacksonView1.class);
|
||||
this.converter.write(bean, ResolvableType.forClass(JacksonViewBean.class), null, outputMessage, hints);
|
||||
|
||||
String result = outputMessage.getBodyAsString(StandardCharsets.UTF_8);
|
||||
assertThat(result).contains("<withView1>with</withView1>");
|
||||
assertThat(result).doesNotContain("<withView2>with</withView2>");
|
||||
assertThat(result).doesNotContain("<withoutView>without</withoutView>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customXmlMapper() {
|
||||
new JacksonXmlHttpMessageConverter(new MyXmlMapper());
|
||||
// Assert no exception is thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithExternalReference() throws IOException {
|
||||
String body = "<!DOCTYPE MyBean SYSTEM \"https://192.168.28.42/1.jsp\" [" +
|
||||
" <!ELEMENT root ANY >\n" +
|
||||
" <!ENTITY ext SYSTEM \"" +
|
||||
new ClassPathResource("external.txt", getClass()).getURI() +
|
||||
"\" >]><MyBean><string>&ext;</string></MyBean>";
|
||||
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() ->
|
||||
this.converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readWithXmlBomb() {
|
||||
// https://en.wikipedia.org/wiki/Billion_laughs
|
||||
// https://msdn.microsoft.com/en-us/magazine/ee335713.aspx
|
||||
String body = """
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE lolz [
|
||||
<!ENTITY lol "lol">
|
||||
<!ELEMENT lolz (#PCDATA)>
|
||||
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
|
||||
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
|
||||
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
|
||||
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
|
||||
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
|
||||
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
|
||||
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
|
||||
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
|
||||
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
|
||||
]>
|
||||
<MyBean>&lol9;</MyBean>""";
|
||||
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.UTF_8));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_XML);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class).isThrownBy(() ->
|
||||
this.converter.read(MyBean.class, inputMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readNonUnicode() throws Exception {
|
||||
String body = "<MyBean>" +
|
||||
"<string>føø bår</string>" +
|
||||
"</MyBean>";
|
||||
|
||||
Charset charset = StandardCharsets.ISO_8859_1;
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "xml", charset));
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("føø bår");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private interface MyJacksonView1 {}
|
||||
|
||||
private interface MyJacksonView2 {}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MyXmlMapper extends XmlMapper {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.dataformat.yaml.YAMLMapper;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
import org.springframework.web.testfixture.http.MockHttpOutputMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.within;
|
||||
|
||||
/**
|
||||
* Jackson 3.x YAML converter tests.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class JacksonYamlHttpMessageConverterTests {
|
||||
|
||||
private final JacksonYamlHttpMessageConverter converter = new JacksonYamlHttpMessageConverter();
|
||||
private final YAMLMapper mapper = YAMLMapper.builder().build();
|
||||
|
||||
|
||||
@Test
|
||||
void canRead() {
|
||||
assertThat(converter.canRead(MyBean.class, MediaType.APPLICATION_YAML)).isTrue();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canRead(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void canWrite() {
|
||||
assertThat(converter.canWrite(MyBean.class, MediaType.APPLICATION_YAML)).isTrue();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "json"))).isFalse();
|
||||
assertThat(converter.canWrite(MyBean.class, new MediaType("application", "xml"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void read() throws IOException {
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(mapper.writeValueAsBytes(body));
|
||||
inputMessage.getHeaders().setContentType(MediaType.APPLICATION_YAML);
|
||||
MyBean result = (MyBean) converter.read(MyBean.class, inputMessage);
|
||||
assertThat(result.getString()).isEqualTo("Foo");
|
||||
assertThat(result.getNumber()).isEqualTo(42);
|
||||
assertThat(result.getFraction()).isCloseTo(42F, within(0F));
|
||||
|
||||
assertThat(result.getArray()).isEqualTo(new String[]{"Foo", "Bar"});
|
||||
assertThat(result.isBool()).isTrue();
|
||||
assertThat(result.getBytes()).isEqualTo(new byte[]{0x1, 0x2});
|
||||
}
|
||||
|
||||
@Test
|
||||
void write() throws IOException {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
MyBean body = new MyBean();
|
||||
body.setString("Foo");
|
||||
body.setNumber(42);
|
||||
body.setFraction(42F);
|
||||
body.setArray(new String[]{"Foo", "Bar"});
|
||||
body.setBool(true);
|
||||
body.setBytes(new byte[]{0x1, 0x2});
|
||||
converter.write(body, null, outputMessage);
|
||||
assertThat(outputMessage.getBodyAsBytes()).isEqualTo(mapper.writeValueAsBytes(body));
|
||||
assertThat(outputMessage.getHeaders().getContentType())
|
||||
.as("Invalid content-type").isEqualTo(MediaType.APPLICATION_YAML);
|
||||
}
|
||||
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
private String string;
|
||||
|
||||
private int number;
|
||||
|
||||
private float fraction;
|
||||
|
||||
private String[] array;
|
||||
|
||||
private boolean bool;
|
||||
|
||||
private byte[] bytes;
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void setBytes(byte[] bytes) {
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isBool() {
|
||||
return bool;
|
||||
}
|
||||
|
||||
public void setBool(boolean bool) {
|
||||
this.bool = bool;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(int number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public float getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
this.fraction = fraction;
|
||||
}
|
||||
|
||||
public String[] getArray() {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setArray(String[] array) {
|
||||
this.array = array;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -522,7 +522,7 @@ class RestClientIntegrationTests {
|
||||
expectRequestCount(1);
|
||||
expectRequest(request -> {
|
||||
assertThat(request.getPath()).isEqualTo("/pojo/capitalize");
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}");
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}");
|
||||
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo("application/json");
|
||||
assertThat(request.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json");
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -30,6 +30,7 @@ import java.util.stream.Stream;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
@@ -407,7 +408,8 @@ class RestTemplateIntegrationTests extends AbstractMockWebServerTests {
|
||||
}
|
||||
|
||||
@ParameterizedRestTemplateTest
|
||||
void jsonPostForObjectWithJacksonView(ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||
@Disabled("Use RestClient + upcoming hint management instead")
|
||||
void jsonPostForObjectWithJacksonJsonView(ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||
setUpClient(clientHttpRequestFactory);
|
||||
|
||||
HttpHeaders entityHeaders = new HttpHeaders();
|
||||
|
||||
@@ -53,8 +53,8 @@ import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.SmartHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.util.DefaultUriBuilderFactory;
|
||||
|
||||
@@ -113,7 +113,7 @@ class RestTemplateTests {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();
|
||||
assertThat(httpMessageConverters).extracting("class").containsOnlyOnce(
|
||||
MappingJackson2HttpMessageConverter.class
|
||||
JacksonJsonHttpMessageConverter.class
|
||||
);
|
||||
assertThat(httpMessageConverters).extracting("class").doesNotContain(
|
||||
KotlinSerializationJsonHttpMessageConverter.class
|
||||
|
||||
@@ -39,6 +39,12 @@ dependencies {
|
||||
optional("org.jetbrains.kotlinx:kotlinx-serialization-protobuf")
|
||||
optional("org.reactivestreams:reactive-streams")
|
||||
optional("org.webjars:webjars-locator-lite")
|
||||
optional("tools.jackson.core:jackson-databind")
|
||||
optional("tools.jackson.dataformat:jackson-dataformat-smile")
|
||||
optional("tools.jackson.dataformat:jackson-dataformat-cbor")
|
||||
optional("tools.jackson.dataformat:jackson-dataformat-smile")
|
||||
optional("tools.jackson.dataformat:jackson-dataformat-xml")
|
||||
optional("tools.jackson.dataformat:jackson-dataformat-yaml")
|
||||
testCompileOnly("com.google.code.findbugs:findbugs") { // for groovy-templates
|
||||
exclude group: "dom4j", module: "dom4j"
|
||||
}
|
||||
|
||||
@@ -45,20 +45,25 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
|
||||
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.Jackson2ObjectMapperBuilder;
|
||||
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.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -196,14 +201,24 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
|
||||
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;
|
||||
@@ -220,12 +235,17 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
|
||||
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);
|
||||
@@ -445,19 +465,19 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
map.put("atom", MediaType.APPLICATION_ATOM_XML);
|
||||
map.put("rss", MediaType.APPLICATION_RSS_XML);
|
||||
}
|
||||
if (jaxb2Present || jackson2XmlPresent) {
|
||||
if (jaxb2Present || jacksonXmlPresent || jackson2XmlPresent) {
|
||||
map.put("xml", MediaType.APPLICATION_XML);
|
||||
}
|
||||
if (jackson2Present || gsonPresent || jsonbPresent || kotlinSerializationJsonPresent) {
|
||||
if (jacksonPresent || jackson2Present || gsonPresent || jsonbPresent || kotlinSerializationJsonPresent) {
|
||||
map.put("json", MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if (jackson2SmilePresent) {
|
||||
if (jacksonSmilePresent || jackson2SmilePresent) {
|
||||
map.put("smile", MediaType.valueOf("application/x-jackson-smile"));
|
||||
}
|
||||
if (jackson2CborPresent || kotlinSerializationCborPresent) {
|
||||
if (jacksonCborPresent || jackson2CborPresent || kotlinSerializationCborPresent) {
|
||||
map.put("cbor", MediaType.APPLICATION_CBOR);
|
||||
}
|
||||
if (jackson2YamlPresent) {
|
||||
if (jacksonYamlPresent || jackson2YamlPresent) {
|
||||
map.put("yaml", MediaType.APPLICATION_YAML);
|
||||
}
|
||||
return map;
|
||||
@@ -679,7 +699,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
|
||||
adapter.setErrorResponseInterceptors(getErrorResponseInterceptors());
|
||||
|
||||
if (jackson2Present) {
|
||||
if (jacksonPresent || jackson2Present) {
|
||||
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
|
||||
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
|
||||
}
|
||||
@@ -909,7 +929,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
messageConverters.add(new RssChannelHttpMessageConverter());
|
||||
}
|
||||
|
||||
if (jackson2XmlPresent) {
|
||||
if (jacksonXmlPresent) {
|
||||
messageConverters.add(new JacksonXmlHttpMessageConverter());
|
||||
}
|
||||
else if (jackson2XmlPresent) {
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
|
||||
if (this.applicationContext != null) {
|
||||
builder.applicationContext(this.applicationContext);
|
||||
@@ -924,7 +947,10 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
messageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
|
||||
}
|
||||
|
||||
if (jackson2Present) {
|
||||
if (jacksonPresent) {
|
||||
messageConverters.add(new JacksonJsonHttpMessageConverter());
|
||||
}
|
||||
else if (jackson2Present) {
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
|
||||
if (this.applicationContext != null) {
|
||||
builder.applicationContext(this.applicationContext);
|
||||
@@ -941,14 +967,21 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
|
||||
}
|
||||
|
||||
if (jackson2SmilePresent) {
|
||||
if (jacksonSmilePresent) {
|
||||
messageConverters.add(new JacksonSmileHttpMessageConverter());
|
||||
}
|
||||
else if (jackson2SmilePresent) {
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
|
||||
if (this.applicationContext != null) {
|
||||
builder.applicationContext(this.applicationContext);
|
||||
}
|
||||
messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
|
||||
}
|
||||
if (jackson2CborPresent) {
|
||||
|
||||
if (jacksonCborPresent) {
|
||||
messageConverters.add(new JacksonCborHttpMessageConverter());
|
||||
}
|
||||
else if (jackson2CborPresent) {
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
|
||||
if (this.applicationContext != null) {
|
||||
builder.applicationContext(this.applicationContext);
|
||||
@@ -958,7 +991,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
else if (kotlinSerializationCborPresent) {
|
||||
messageConverters.add(new KotlinSerializationCborHttpMessageConverter());
|
||||
}
|
||||
if (jackson2YamlPresent) {
|
||||
|
||||
if (jacksonYamlPresent) {
|
||||
messageConverters.add(new JacksonYamlHttpMessageConverter());
|
||||
}
|
||||
else if (jackson2YamlPresent) {
|
||||
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.yaml();
|
||||
if (this.applicationContext != null) {
|
||||
builder.applicationContext(this.applicationContext);
|
||||
@@ -1084,7 +1121,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
|
||||
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
|
||||
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
|
||||
exceptionHandlerResolver.setErrorResponseInterceptors(getErrorResponseInterceptors());
|
||||
if (jackson2Present) {
|
||||
if (jacksonPresent || jackson2Present) {
|
||||
exceptionHandlerResolver.setResponseBodyAdvice(
|
||||
Collections.singletonList(new JsonViewResponseBodyAdvice()));
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.springframework.context.MessageSourceResolvable;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.validation.Validator;
|
||||
@@ -138,7 +138,7 @@ class MethodValidationTests {
|
||||
handlerAdapter.setApplicationContext(context);
|
||||
handlerAdapter.setBeanFactory(context.getBeanFactory());
|
||||
handlerAdapter.setMessageConverters(
|
||||
List.of(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter()));
|
||||
List.of(new StringHttpMessageConverter(), new JacksonJsonHttpMessageConverter()));
|
||||
handlerAdapter.afterPropertiesSet();
|
||||
return handlerAdapter;
|
||||
}
|
||||
|
||||
@@ -28,12 +28,13 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.xmlunit.assertj.XmlAssert;
|
||||
import tools.jackson.databind.SerializationFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.SingletonTargetSource;
|
||||
@@ -51,8 +52,10 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -69,8 +72,6 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
@@ -116,7 +117,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -151,7 +152,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType("application/json");
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
SimpleBean result = (SimpleBean) processor.resolveArgument(
|
||||
@@ -207,7 +208,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
SimpleBean result = (SimpleBean) processor.resolveArgument(methodParam, container, request, factory);
|
||||
@@ -226,7 +227,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -246,7 +247,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
HttpMessageConverter<Object> target = new MappingJackson2HttpMessageConverter();
|
||||
HttpMessageConverter<Object> target = new JacksonJsonHttpMessageConverter();
|
||||
HttpMessageConverter<?> proxy = ProxyFactory.getProxy(HttpMessageConverter.class, new SingletonTargetSource(target));
|
||||
List<HttpMessageConverter<?>> converters = List.of(proxy);
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
@@ -262,7 +263,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
this.servletRequest.addHeader("Accept", "text/plain; q=0.5, application/json");
|
||||
|
||||
List<HttpMessageConverter<?>> converters =
|
||||
List.of(new MappingJackson2HttpMessageConverter(), new StringHttpMessageConverter());
|
||||
List.of(new JacksonJsonHttpMessageConverter(), new StringHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
processor.writeWithMessageConverters("Foo", returnTypeString, request);
|
||||
@@ -331,15 +332,14 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
|
||||
this.servletRequest.addHeader("Accept", halFormsMediaType + "," + halMediaType);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
JsonMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||
|
||||
SimpleBean simpleBean = new SimpleBean();
|
||||
simpleBean.setId(12L);
|
||||
simpleBean.setName("Jason");
|
||||
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
converter.registerObjectMappersForType(SimpleBean.class, map -> map.put(halMediaType, objectMapper));
|
||||
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter();
|
||||
converter.registerObjectMappersForType(SimpleBean.class, map -> map.put(halMediaType, mapper));
|
||||
RequestResponseBodyMethodProcessor processor =
|
||||
new RequestResponseBodyMethodProcessor(List.of(converter));
|
||||
MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("getSimpleBean"), -1);
|
||||
@@ -380,7 +380,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
|
||||
RequestResponseBodyMethodProcessor processor =
|
||||
new RequestResponseBodyMethodProcessor(List.of(
|
||||
new MappingJackson2HttpMessageConverter(), new MappingJackson2XmlHttpMessageConverter()));
|
||||
new JacksonJsonHttpMessageConverter(), new JacksonXmlHttpMessageConverter()));
|
||||
|
||||
MethodParameter returnType =
|
||||
new MethodParameter(getClass().getDeclaredMethod("handleAndReturnProblemDetail"), -1);
|
||||
@@ -393,10 +393,10 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
if (expectedContentType.equals(MediaType.APPLICATION_PROBLEM_XML_VALUE)) {
|
||||
XmlAssert.assertThat(this.servletResponse.getContentAsString()).and("""
|
||||
<problem xmlns="urn:ietf:rfc:7807">
|
||||
<type>about:blank</type>
|
||||
<title>Bad Request</title>
|
||||
<status>400</status>
|
||||
<instance>/path</instance>
|
||||
<title>Bad Request</title>
|
||||
<type>about:blank</type>
|
||||
</problem>""")
|
||||
.ignoreWhitespace()
|
||||
.areIdentical();
|
||||
@@ -413,6 +413,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("https://github.com/FasterXML/jackson-dataformat-xml/issues/757")
|
||||
void problemDetailWhenProblemXmlRequested() throws Exception {
|
||||
this.servletRequest.addHeader("Accept", MediaType.APPLICATION_PROBLEM_XML_VALUE);
|
||||
testProblemDetailMediaType(MediaType.APPLICATION_PROBLEM_XML_VALUE);
|
||||
@@ -504,6 +505,25 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
|
||||
Object returnValue = new JacksonController().handleResponseBody();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.doesNotContain("\"withView1\":\"with\"")
|
||||
.contains("\"withView2\":\"with\"")
|
||||
.doesNotContain("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jackson2JsonViewWithResponseBodyAndJsonMessageConverter() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleResponseBody");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
@@ -523,6 +543,25 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
|
||||
Object returnValue = new JacksonController().handleResponseEntity();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.doesNotContain("\"withView1\":\"with\"")
|
||||
.contains("\"withView2\":\"with\"")
|
||||
.doesNotContain("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jackson2JsonViewWithResponseEntityAndJsonMessageConverter() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleResponseEntity");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
@@ -536,12 +575,31 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
.doesNotContain("\"withoutView\":\"without\"");
|
||||
}
|
||||
|
||||
@Test // SPR-12149
|
||||
@Test
|
||||
void jacksonJsonViewWithResponseBodyAndXmlMessageConverter() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleResponseBody");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonXmlHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
|
||||
Object returnValue = new JacksonController().handleResponseBody();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.doesNotContain("<withView1>with</withView1>")
|
||||
.contains("<withView2>with</withView2>")
|
||||
.doesNotContain("<withoutView>without</withoutView>");
|
||||
}
|
||||
|
||||
@Test // SPR-12149
|
||||
void jackson2JsonViewWithResponseBodyAndXmlMessageConverter() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleResponseBody");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2XmlHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
@@ -555,12 +613,31 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
.doesNotContain("<withoutView>without</withoutView>");
|
||||
}
|
||||
|
||||
@Test // SPR-12149
|
||||
@Test
|
||||
void jacksonJsonViewWithResponseEntityAndXmlMessageConverter() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleResponseEntity");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonXmlHttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
|
||||
Object returnValue = new JacksonController().handleResponseEntity();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.doesNotContain("<withView1>with</withView1>")
|
||||
.contains("<withView2>with</withView2>")
|
||||
.doesNotContain("<withoutView>without</withoutView>");
|
||||
}
|
||||
|
||||
@Test // SPR-12149
|
||||
void jackson2JsonViewWithResponseEntityAndXmlMessageConverter() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleResponseEntity");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2XmlHttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewResponseBodyAdvice()));
|
||||
@@ -574,7 +651,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
.doesNotContain("<withoutView>without</withoutView>");
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
@Test
|
||||
void resolveArgumentWithJacksonJsonView() throws Exception {
|
||||
String content = "{\"withView1\" : \"with\", \"withView2\" : \"with\", \"withoutView\" : \"without\"}";
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
@@ -584,6 +661,29 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
|
||||
JacksonViewBean result = (JacksonViewBean)
|
||||
processor.resolveArgument(methodParameter, this.container, this.request, this.factory);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getWithView1()).isEqualTo("with");
|
||||
assertThat(result.getWithView2()).isNull();
|
||||
assertThat(result.getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
void resolveArgumentWithJackson2JsonView() throws Exception {
|
||||
String content = "{\"withView1\" : \"with\", \"withView2\" : \"with\", \"withoutView\" : \"without\"}";
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
Method method = JacksonController.class.getMethod("handleRequestBody", JacksonViewBean.class);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
@@ -597,7 +697,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
assertThat(result.getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
@Test
|
||||
void resolveHttpEntityArgumentWithJacksonJsonView() throws Exception {
|
||||
String content = "{\"withView1\" : \"with\", \"withView2\" : \"with\", \"withoutView\" : \"without\"}";
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
@@ -607,6 +707,31 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
HttpEntity<JacksonViewBean> result = (HttpEntity<JacksonViewBean>)
|
||||
processor.resolveArgument( methodParameter, this.container, this.request, this.factory);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getBody()).isNotNull();
|
||||
assertThat(result.getBody().getWithView1()).isEqualTo("with");
|
||||
assertThat(result.getBody().getWithView2()).isNull();
|
||||
assertThat(result.getBody().getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
void resolveHttpEntityArgumentWithJackson2JsonView() throws Exception {
|
||||
String content = "{\"withView1\" : \"with\", \"withView2\" : \"with\", \"withoutView\" : \"without\"}";
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
Method method = JacksonController.class.getMethod("handleHttpEntity", HttpEntity.class);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
@@ -622,7 +747,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
assertThat(result.getBody().getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
@Test
|
||||
void resolveArgumentWithJacksonJsonViewAndXmlMessageConverter() throws Exception {
|
||||
String content = "<root>" +
|
||||
"<withView1>with</withView1>" +
|
||||
@@ -635,6 +760,32 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonXmlHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
|
||||
JacksonViewBean result = (JacksonViewBean)
|
||||
processor.resolveArgument(methodParameter, this.container, this.request, this.factory);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getWithView1()).isEqualTo("with");
|
||||
assertThat(result.getWithView2()).isNull();
|
||||
assertThat(result.getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
void resolveArgumentWithJackson2JsonViewAndXmlMessageConverter() throws Exception {
|
||||
String content = "<root>" +
|
||||
"<withView1>with</withView1>" +
|
||||
"<withView2>with</withView2>" +
|
||||
"<withoutView>without</withoutView></root>";
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
|
||||
Method method = JacksonController.class.getMethod("handleRequestBody", JacksonViewBean.class);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2XmlHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
@@ -648,7 +799,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
assertThat(result.getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
@Test
|
||||
void resolveHttpEntityArgumentWithJacksonJsonViewAndXmlMessageConverter() throws Exception {
|
||||
String content = "<root>" +
|
||||
"<withView1>with</withView1>" +
|
||||
@@ -661,6 +812,34 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonXmlHttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
HttpEntity<JacksonViewBean> result = (HttpEntity<JacksonViewBean>)
|
||||
processor.resolveArgument(methodParameter, this.container, this.request, this.factory);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getBody()).isNotNull();
|
||||
assertThat(result.getBody().getWithView1()).isEqualTo("with");
|
||||
assertThat(result.getBody().getWithView2()).isNull();
|
||||
assertThat(result.getBody().getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12501
|
||||
void resolveHttpEntityArgumentWithJackson2JsonViewAndXmlMessageConverter() throws Exception {
|
||||
String content = "<root>" +
|
||||
"<withView1>with</withView1>" +
|
||||
"<withView2>with</withView2>" +
|
||||
"<withoutView>without</withoutView></root>";
|
||||
this.servletRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
this.servletRequest.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
|
||||
Method method = JacksonController.class.getMethod("handleHttpEntity", HttpEntity.class);
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2XmlHttpMessageConverter());
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
converters, null, List.of(new JsonViewRequestBodyAdvice()));
|
||||
@@ -676,12 +855,29 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
assertThat(result.getBody().getWithoutView()).isNull();
|
||||
}
|
||||
|
||||
@Test // SPR-12811
|
||||
@Test
|
||||
void jacksonTypeInfoList() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleTypeInfoList");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
Object returnValue = new JacksonController().handleTypeInfoList();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.contains("\"type\":\"foo\"")
|
||||
.contains("\"type\":\"bar\"");
|
||||
}
|
||||
|
||||
@Test // SPR-12811
|
||||
void jackson2TypeInfoList() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleTypeInfoList");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
@@ -693,12 +889,29 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
.contains("\"type\":\"bar\"");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
@Test
|
||||
void jacksonSubType() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleSubType");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
Object returnValue = new JacksonController().handleSubType();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.contains("\"id\":123")
|
||||
.contains("\"name\":\"foo\"");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
void jackson2SubType() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleSubType");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
@@ -710,12 +923,31 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
.contains("\"name\":\"foo\"");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
@Test
|
||||
void jacksonSubTypeList() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleSubTypeList");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
Object returnValue = new JacksonController().handleSubTypeList();
|
||||
processor.handleReturnValue(returnValue, methodReturnType, this.container, this.request);
|
||||
|
||||
assertThat(this.servletResponse.getContentAsString())
|
||||
.contains("\"id\":123")
|
||||
.contains("\"name\":\"foo\"")
|
||||
.contains("\"id\":456")
|
||||
.contains("\"name\":\"bar\"");
|
||||
}
|
||||
|
||||
@Test // SPR-13318
|
||||
void jackson2SubTypeList() throws Exception {
|
||||
Method method = JacksonController.class.getMethod("handleSubTypeList");
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new JacksonController(), method);
|
||||
MethodParameter methodReturnType = handlerMethod.getReturnType();
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
@@ -738,7 +970,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new MyControllerImplementingInterface(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
assertThat(processor.supportsParameter(methodParameter)).isTrue();
|
||||
@@ -756,7 +988,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new SubControllerImplementingInterface(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
assertThat(processor.supportsParameter(methodParameter)).isTrue();
|
||||
@@ -774,7 +1006,7 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
HandlerMethod handlerMethod = new HandlerMethod(new SubControllerImplementingAbstractMethod(), method);
|
||||
MethodParameter methodParameter = handlerMethod.getMethodParameters()[0];
|
||||
|
||||
List<HttpMessageConverter<?>> converters = List.of(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = List.of(new JacksonJsonHttpMessageConverter());
|
||||
RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor(converters);
|
||||
|
||||
assertThat(processor.supportsParameter(methodParameter)).isTrue();
|
||||
@@ -1068,8 +1300,6 @@ class RequestResponseBodyMethodProcessorTests {
|
||||
bean.setWithView1("with");
|
||||
bean.setWithView2("with");
|
||||
bean.setWithoutView("without");
|
||||
ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
|
||||
mav.addObject("bean", bean);
|
||||
return new ResponseEntity<>(bean, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import reactor.core.scheduler.Schedulers;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.async.AsyncWebRequest;
|
||||
@@ -63,7 +63,7 @@ import static org.springframework.web.testfixture.method.ResolvableMethod.on;
|
||||
class ResponseBodyEmitterReturnValueHandlerTests {
|
||||
|
||||
private final ResponseBodyEmitterReturnValueHandler handler =
|
||||
new ResponseBodyEmitterReturnValueHandler(List.of(new MappingJackson2HttpMessageConverter()));
|
||||
new ResponseBodyEmitterReturnValueHandler(List.of(new JacksonJsonHttpMessageConverter()));
|
||||
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@@ -373,7 +373,7 @@ class ServletInvocableHandlerMethodTests {
|
||||
|
||||
@Test
|
||||
void wrapConcurrentResult_CollectedValuesList() throws Exception {
|
||||
List<HttpMessageConverter<?>> converters = Collections.singletonList(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = Collections.singletonList(new JacksonJsonHttpMessageConverter());
|
||||
ResolvableType elementType = ResolvableType.forClass(List.class);
|
||||
ReactiveTypeHandler.CollectedValuesList result = new ReactiveTypeHandler.CollectedValuesList(elementType);
|
||||
result.add(Arrays.asList("foo1", "bar1"));
|
||||
@@ -391,7 +391,7 @@ class ServletInvocableHandlerMethodTests {
|
||||
|
||||
@Test // SPR-15478
|
||||
public void wrapConcurrentResult_CollectedValuesListWithResponseEntity() throws Exception {
|
||||
List<HttpMessageConverter<?>> converters = Collections.singletonList(new MappingJackson2HttpMessageConverter());
|
||||
List<HttpMessageConverter<?>> converters = Collections.singletonList(new JacksonJsonHttpMessageConverter());
|
||||
ResolvableType elementType = ResolvableType.forClass(Bar.class);
|
||||
ReactiveTypeHandler.CollectedValuesList result = new ReactiveTypeHandler.CollectedValuesList(elementType);
|
||||
result.add(new Bar("foo"));
|
||||
|
||||
Reference in New Issue
Block a user