Browse Source

Use JsonMapper instead of ObjectMapper when relevant

This commit updates Jackson 3 JSON support to use JsonMapper
instead of ObjectMapper in converters, codecs and view constructors.

As a consequence, AbstractJacksonDecoder, AbstractJacksonEncoder,
AbstractJacksonHttpMessageConverter and JacksonCodecSupport are
now parameterized with <T extends ObjectMapper>.

Closes gh-35282
pull/35374/head
Sébastien Deleuze 6 months ago
parent
commit
dc26aaa0ec
  1. 33
      spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java
  2. 45
      spring-messaging/src/main/java/org/springframework/messaging/converter/JacksonJsonMessageConverter.java
  3. 4
      spring-test/src/test/java/org/springframework/test/json/AbstractJsonContentAssertTests.java
  4. 4
      spring-test/src/test/java/org/springframework/test/json/JsonPathValueAssertTests.java
  5. 16
      spring-test/src/test/java/org/springframework/test/util/JsonPathExpectationsHelperTests.java
  6. 6
      spring-test/src/test/java/org/springframework/test/web/reactive/server/EncoderDecoderMappingProviderTests.java
  7. 8
      spring-test/src/test/java/org/springframework/test/web/reactive/server/JsonEncoderDecoderTests.java
  8. 18
      spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonDecoder.java
  9. 15
      spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java
  10. 69
      spring-web/src/main/java/org/springframework/http/codec/JacksonCodecSupport.java
  11. 2
      spring-web/src/main/java/org/springframework/http/codec/cbor/JacksonCborDecoder.java
  12. 2
      spring-web/src/main/java/org/springframework/http/codec/cbor/JacksonCborEncoder.java
  13. 11
      spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java
  14. 11
      spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonEncoder.java
  15. 2
      spring-web/src/main/java/org/springframework/http/codec/smile/JacksonSmileDecoder.java
  16. 2
      spring-web/src/main/java/org/springframework/http/codec/smile/JacksonSmileEncoder.java
  17. 2
      spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java
  18. 93
      spring-web/src/main/java/org/springframework/http/converter/AbstractJacksonHttpMessageConverter.java
  19. 2
      spring-web/src/main/java/org/springframework/http/converter/cbor/JacksonCborHttpMessageConverter.java
  20. 9
      spring-web/src/main/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverter.java
  21. 2
      spring-web/src/main/java/org/springframework/http/converter/smile/JacksonSmileHttpMessageConverter.java
  22. 2
      spring-web/src/main/java/org/springframework/http/converter/xml/JacksonXmlHttpMessageConverter.java
  23. 2
      spring-web/src/main/java/org/springframework/http/converter/yaml/JacksonYamlHttpMessageConverter.java
  24. 15
      spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonDecoderTests.java
  25. 7
      spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonEncoderTests.java
  26. 16
      spring-web/src/test/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverterTests.java
  27. 5
      spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java
  28. 9
      spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/JacksonJsonView.java
  29. 10
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java
  30. 2
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java
  31. 5
      spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java
  32. 2
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java
  33. 3
      spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/JacksonJsonViewTests.java
  34. 17
      spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/JacksonJsonSockJsMessageCodec.java

33
spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java

@ -32,7 +32,6 @@ import jakarta.jms.Session;
import jakarta.jms.TextMessage; import jakarta.jms.TextMessage;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.databind.JavaType; import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectWriter; import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -63,7 +62,7 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
public static final String DEFAULT_ENCODING = "UTF-8"; public static final String DEFAULT_ENCODING = "UTF-8";
private final ObjectMapper objectMapper; private final JsonMapper jsonMapper;
private MessageType targetType = MessageType.BYTES; private MessageType targetType = MessageType.BYTES;
@ -86,17 +85,17 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
* {@link MapperBuilder#findModules(ClassLoader)}. * {@link MapperBuilder#findModules(ClassLoader)}.
*/ */
public JacksonJsonMessageConverter() { public JacksonJsonMessageConverter() {
this.objectMapper = JsonMapper.builder().findAndAddModules(JacksonJsonMessageConverter.class.getClassLoader()).build(); this.jsonMapper = JsonMapper.builder().findAndAddModules(JacksonJsonMessageConverter.class.getClassLoader()).build();
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link JsonMapper}.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonMessageConverter(ObjectMapper objectMapper) { public JacksonJsonMessageConverter(JsonMapper jsonMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null"); Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.objectMapper = objectMapper; this.jsonMapper = jsonMapper;
} }
/** /**
@ -173,9 +172,9 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
Message message; Message message;
try { try {
message = switch (this.targetType) { message = switch (this.targetType) {
case TEXT -> mapToTextMessage(object, session, this.objectMapper.writer()); case TEXT -> mapToTextMessage(object, session, this.jsonMapper.writer());
case BYTES -> mapToBytesMessage(object, session, this.objectMapper.writer()); case BYTES -> mapToBytesMessage(object, session, this.jsonMapper.writer());
default -> mapToMessage(object, session, this.objectMapper.writer(), this.targetType); default -> mapToMessage(object, session, this.jsonMapper.writer(), this.targetType);
}; };
} }
catch (IOException ex) { catch (IOException ex) {
@ -206,10 +205,10 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
throws JMSException, MessageConversionException { throws JMSException, MessageConversionException {
if (jsonView != null) { if (jsonView != null) {
return toMessage(object, session, this.objectMapper.writerWithView(jsonView)); return toMessage(object, session, this.jsonMapper.writerWithView(jsonView));
} }
else { else {
return toMessage(object, session, this.objectMapper.writer()); return toMessage(object, session, this.jsonMapper.writer());
} }
} }
@ -363,7 +362,7 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
throws JMSException, IOException { throws JMSException, IOException {
String body = message.getText(); String body = message.getText();
return this.objectMapper.readValue(body, targetJavaType); return this.jsonMapper.readValue(body, targetJavaType);
} }
/** /**
@ -386,7 +385,7 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
if (encoding != null) { if (encoding != null) {
try { try {
String body = new String(bytes, encoding); String body = new String(bytes, encoding);
return this.objectMapper.readValue(body, targetJavaType); return this.jsonMapper.readValue(body, targetJavaType);
} }
catch (UnsupportedEncodingException ex) { catch (UnsupportedEncodingException ex) {
throw new MessageConversionException("Cannot convert bytes to String", ex); throw new MessageConversionException("Cannot convert bytes to String", ex);
@ -394,7 +393,7 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
} }
else { else {
// Jackson internally performs encoding detection, falling back to UTF-8. // Jackson internally performs encoding detection, falling back to UTF-8.
return this.objectMapper.readValue(bytes, targetJavaType); return this.jsonMapper.readValue(bytes, targetJavaType);
} }
} }
@ -437,11 +436,11 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
} }
Class<?> mappedClass = this.idClassMappings.get(typeId); Class<?> mappedClass = this.idClassMappings.get(typeId);
if (mappedClass != null) { if (mappedClass != null) {
return this.objectMapper.constructType(mappedClass); return this.jsonMapper.constructType(mappedClass);
} }
try { try {
Class<?> typeClass = ClassUtils.forName(typeId, this.beanClassLoader); Class<?> typeClass = ClassUtils.forName(typeId, this.beanClassLoader);
return this.objectMapper.constructType(typeClass); return this.jsonMapper.constructType(typeClass);
} }
catch (Throwable ex) { catch (Throwable ex) {
throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex); throw new MessageConversionException("Failed to resolve type id [" + typeId + "]", ex);

45
spring-messaging/src/main/java/org/springframework/messaging/converter/JacksonJsonMessageConverter.java

@ -27,7 +27,6 @@ import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonEncoding; import tools.jackson.core.JsonEncoding;
import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.JavaType; import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -52,7 +51,7 @@ public class JacksonJsonMessageConverter extends AbstractMessageConverter {
private static final MimeType[] DEFAULT_MIME_TYPES = new MimeType[] { private static final MimeType[] DEFAULT_MIME_TYPES = new MimeType[] {
new MimeType("application", "json"), new MimeType("application", "*+json")}; new MimeType("application", "json"), new MimeType("application", "*+json")};
private final ObjectMapper objectMapper; private final JsonMapper jsonMapper;
/** /**
@ -73,35 +72,35 @@ public class JacksonJsonMessageConverter extends AbstractMessageConverter {
*/ */
public JacksonJsonMessageConverter(MimeType... supportedMimeTypes) { public JacksonJsonMessageConverter(MimeType... supportedMimeTypes) {
super(supportedMimeTypes); super(supportedMimeTypes);
this.objectMapper = JsonMapper.builder().findAndAddModules(JacksonJsonMessageConverter.class.getClassLoader()).build(); this.jsonMapper = JsonMapper.builder().findAndAddModules(JacksonJsonMessageConverter.class.getClassLoader()).build();
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link JsonMapper}.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonMessageConverter(ObjectMapper objectMapper) { public JacksonJsonMessageConverter(JsonMapper jsonMapper) {
this(objectMapper, DEFAULT_MIME_TYPES); this(jsonMapper, DEFAULT_MIME_TYPES);
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and the * Construct a new instance with the provided {@link JsonMapper} and the
* provided {@link MimeType}s. * provided {@link MimeType}s.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonMessageConverter(ObjectMapper objectMapper, MimeType... supportedMimeTypes) { public JacksonJsonMessageConverter(JsonMapper jsonMapper, MimeType... supportedMimeTypes) {
super(supportedMimeTypes); super(supportedMimeTypes);
Assert.notNull(objectMapper, "ObjectMapper must not be null"); Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.objectMapper = objectMapper; this.jsonMapper = jsonMapper;
} }
/** /**
* Return the underlying {@code ObjectMapper} for this converter. * Return the underlying {@code JsonMapper} for this converter.
*/ */
protected ObjectMapper getObjectMapper() { protected JsonMapper getJsonMapper() {
return this.objectMapper; return this.jsonMapper;
} }
@Override @Override
@ -122,7 +121,7 @@ public class JacksonJsonMessageConverter extends AbstractMessageConverter {
@Override @Override
protected @Nullable Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) { protected @Nullable Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
JavaType javaType = this.objectMapper.constructType(getResolvedType(targetClass, conversionHint)); JavaType javaType = this.jsonMapper.constructType(getResolvedType(targetClass, conversionHint));
Object payload = message.getPayload(); Object payload = message.getPayload();
Class<?> view = getSerializationView(conversionHint); Class<?> view = getSerializationView(conversionHint);
try { try {
@ -131,19 +130,19 @@ public class JacksonJsonMessageConverter extends AbstractMessageConverter {
} }
else if (payload instanceof byte[] bytes) { else if (payload instanceof byte[] bytes) {
if (view != null) { if (view != null) {
return this.objectMapper.readerWithView(view).forType(javaType).readValue(bytes); return this.jsonMapper.readerWithView(view).forType(javaType).readValue(bytes);
} }
else { else {
return this.objectMapper.readValue(bytes, javaType); return this.jsonMapper.readValue(bytes, javaType);
} }
} }
else { else {
// Assuming a text-based source payload // Assuming a text-based source payload
if (view != null) { if (view != null) {
return this.objectMapper.readerWithView(view).forType(javaType).readValue(payload.toString()); return this.jsonMapper.readerWithView(view).forType(javaType).readValue(payload.toString());
} }
else { else {
return this.objectMapper.readValue(payload.toString(), javaType); return this.jsonMapper.readValue(payload.toString(), javaType);
} }
} }
} }
@ -161,12 +160,12 @@ public class JacksonJsonMessageConverter extends AbstractMessageConverter {
if (byte[].class == getSerializedPayloadClass()) { if (byte[].class == getSerializedPayloadClass()) {
ByteArrayOutputStream out = new ByteArrayOutputStream(1024); ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
JsonEncoding encoding = getJsonEncoding(getMimeType(headers)); JsonEncoding encoding = getJsonEncoding(getMimeType(headers));
try (JsonGenerator generator = this.objectMapper.createGenerator(out, encoding)) { try (JsonGenerator generator = this.jsonMapper.createGenerator(out, encoding)) {
if (view != null) { if (view != null) {
this.objectMapper.writerWithView(view).writeValue(generator, payload); this.jsonMapper.writerWithView(view).writeValue(generator, payload);
} }
else { else {
this.objectMapper.writeValue(generator, payload); this.jsonMapper.writeValue(generator, payload);
} }
payload = out.toByteArray(); payload = out.toByteArray();
} }
@ -175,10 +174,10 @@ public class JacksonJsonMessageConverter extends AbstractMessageConverter {
// Assuming a text-based target payload // Assuming a text-based target payload
Writer writer = new StringWriter(1024); Writer writer = new StringWriter(1024);
if (view != null) { if (view != null) {
this.objectMapper.writerWithView(view).writeValue(writer, payload); this.jsonMapper.writerWithView(view).writeValue(writer, payload);
} }
else { else {
this.objectMapper.writeValue(writer, payload); this.jsonMapper.writeValue(writer, payload);
} }
payload = writer.toString(); payload = writer.toString();
} }

4
spring-test/src/test/java/org/springframework/test/json/AbstractJsonContentAssertTests.java

@ -45,7 +45,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.skyscreamer.jsonassert.JSONCompareMode; import org.skyscreamer.jsonassert.JSONCompareMode;
import org.skyscreamer.jsonassert.JSONCompareResult; import org.skyscreamer.jsonassert.JSONCompareResult;
import org.skyscreamer.jsonassert.comparator.JSONComparator; import org.skyscreamer.jsonassert.comparator.JSONComparator;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
@ -86,7 +86,7 @@ class AbstractJsonContentAssertTests {
private static final String DIFFERENT = loadJson("different.json"); private static final String DIFFERENT = loadJson("different.json");
private static final HttpMessageContentConverter jsonContentConverter = HttpMessageContentConverter.of( private static final HttpMessageContentConverter jsonContentConverter = HttpMessageContentConverter.of(
new JacksonJsonHttpMessageConverter(new ObjectMapper())); new JacksonJsonHttpMessageConverter(new JsonMapper()));
private static final JsonComparator comparator = JsonAssert.comparator(JsonCompareMode.LENIENT); private static final JsonComparator comparator = JsonAssert.comparator(JsonCompareMode.LENIENT);

4
spring-test/src/test/java/org/springframework/test/json/JsonPathValueAssertTests.java

@ -27,7 +27,7 @@ import org.assertj.core.data.Offset;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
import org.springframework.test.http.HttpMessageContentConverter; import org.springframework.test.http.HttpMessageContentConverter;
@ -206,7 +206,7 @@ class JsonPathValueAssertTests {
class ConvertToTests { class ConvertToTests {
private static final HttpMessageContentConverter jsonContentConverter = HttpMessageContentConverter.of( private static final HttpMessageContentConverter jsonContentConverter = HttpMessageContentConverter.of(
new JacksonJsonHttpMessageConverter(new ObjectMapper())); new JacksonJsonHttpMessageConverter(new JsonMapper()));
@Test @Test
void convertToWithoutHttpMessageConverter() { void convertToWithoutHttpMessageConverter() {

16
spring-test/src/test/java/org/springframework/test/util/JsonPathExpectationsHelperTests.java

@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import tools.jackson.databind.JavaType; import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
@ -385,14 +385,14 @@ class JsonPathExpectationsHelperTests {
*/ */
private static class JacksonMappingProvider implements MappingProvider { private static class JacksonMappingProvider implements MappingProvider {
private final ObjectMapper objectMapper; private final JsonMapper jsonMapper;
public JacksonMappingProvider() { public JacksonMappingProvider() {
this(new ObjectMapper()); this(new JsonMapper());
} }
public JacksonMappingProvider(ObjectMapper objectMapper) { public JacksonMappingProvider(JsonMapper jsonMapper) {
this.objectMapper = objectMapper; this.jsonMapper = jsonMapper;
} }
@ -402,7 +402,7 @@ class JsonPathExpectationsHelperTests {
return null; return null;
} }
try { try {
return objectMapper.convertValue(source, targetType); return jsonMapper.convertValue(source, targetType);
} }
catch (Exception ex) { catch (Exception ex) {
throw new MappingException(ex); throw new MappingException(ex);
@ -416,10 +416,10 @@ class JsonPathExpectationsHelperTests {
if (source == null){ if (source == null){
return null; return null;
} }
JavaType type = objectMapper.getTypeFactory().constructType(targetType.getType()); JavaType type = jsonMapper.getTypeFactory().constructType(targetType.getType());
try { try {
return (T) objectMapper.convertValue(source, type); return (T) jsonMapper.convertValue(source, type);
} }
catch (Exception ex) { catch (Exception ex) {
throw new MappingException(ex); throw new MappingException(ex);

6
spring-test/src/test/java/org/springframework/test/web/reactive/server/EncoderDecoderMappingProviderTests.java

@ -22,7 +22,7 @@ import java.util.Map;
import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.TypeRef; import com.jayway.jsonpath.TypeRef;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.codec.json.JacksonJsonDecoder; import org.springframework.http.codec.json.JacksonJsonDecoder;
import org.springframework.http.codec.json.JacksonJsonEncoder; import org.springframework.http.codec.json.JacksonJsonEncoder;
@ -36,10 +36,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class EncoderDecoderMappingProviderTests { class EncoderDecoderMappingProviderTests {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final JsonMapper jsonMapper = new JsonMapper();
private final EncoderDecoderMappingProvider mappingProvider = new EncoderDecoderMappingProvider( private final EncoderDecoderMappingProvider mappingProvider = new EncoderDecoderMappingProvider(
new JacksonJsonEncoder(objectMapper), new JacksonJsonDecoder(objectMapper)); new JacksonJsonEncoder(jsonMapper), new JacksonJsonDecoder(jsonMapper));
@Test @Test

8
spring-test/src/test/java/org/springframework/test/web/reactive/server/JsonEncoderDecoderTests.java

@ -19,7 +19,7 @@ package org.springframework.test.web.reactive.server;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.EncoderHttpMessageWriter;
@ -39,13 +39,13 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class JsonEncoderDecoderTests { class JsonEncoderDecoderTests {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final JsonMapper jsonMapper = new JsonMapper();
private static final HttpMessageWriter<?> jacksonMessageWriter = new EncoderHttpMessageWriter<>( private static final HttpMessageWriter<?> jacksonMessageWriter = new EncoderHttpMessageWriter<>(
new JacksonJsonEncoder(objectMapper)); new JacksonJsonEncoder(jsonMapper));
private static final HttpMessageReader<?> jacksonMessageReader = new DecoderHttpMessageReader<>( private static final HttpMessageReader<?> jacksonMessageReader = new DecoderHttpMessageReader<>(
new JacksonJsonDecoder(objectMapper)); new JacksonJsonDecoder(jsonMapper));
@Test @Test
void fromWithEmptyWriters() { void fromWithEmptyWriters() {

18
spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonDecoder.java

@ -57,8 +57,9 @@ import org.springframework.util.MimeType;
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
* @param <T> the type of {@link ObjectMapper}
*/ */
public abstract class AbstractJacksonDecoder extends JacksonCodecSupport implements HttpMessageDecoder<Object> { public abstract class AbstractJacksonDecoder<T extends ObjectMapper> extends JacksonCodecSupport<T> implements HttpMessageDecoder<Object> {
private int maxInMemorySize = 256 * 1024; private int maxInMemorySize = 256 * 1024;
@ -68,14 +69,14 @@ public abstract class AbstractJacksonDecoder extends JacksonCodecSupport impleme
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s. * by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s.
*/ */
protected AbstractJacksonDecoder(MapperBuilder<?, ?> builder, MimeType... mimeTypes) { protected AbstractJacksonDecoder(MapperBuilder<T, ?> builder, MimeType... mimeTypes) {
super(builder, mimeTypes); super(builder, mimeTypes);
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MimeType}s. * Construct a new instance with the provided {@link ObjectMapper} and {@link MimeType}s.
*/ */
protected AbstractJacksonDecoder(ObjectMapper mapper, MimeType... mimeTypes) { protected AbstractJacksonDecoder(T mapper, MimeType... mimeTypes) {
super(mapper, mimeTypes); super(mapper, mimeTypes);
} }
@ -104,7 +105,7 @@ public abstract class AbstractJacksonDecoder extends JacksonCodecSupport impleme
if (!supportsMimeType(mimeType)) { if (!supportsMimeType(mimeType)) {
return false; return false;
} }
ObjectMapper mapper = selectObjectMapper(elementType, mimeType); T mapper = selectMapper(elementType, mimeType);
if (mapper == null) { if (mapper == null) {
return false; return false;
} }
@ -115,7 +116,7 @@ public abstract class AbstractJacksonDecoder extends JacksonCodecSupport impleme
public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType, public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
ObjectMapper mapper = selectObjectMapper(elementType, mimeType); T mapper = selectMapper(elementType, mimeType);
if (mapper == null) { if (mapper == null) {
return Flux.error(new IllegalStateException("No ObjectMapper for " + elementType)); return Flux.error(new IllegalStateException("No ObjectMapper for " + elementType));
} }
@ -141,7 +142,7 @@ public abstract class AbstractJacksonDecoder extends JacksonCodecSupport impleme
return tokens.handle((tokenBuffer, sink) -> { return tokens.handle((tokenBuffer, sink) -> {
try { try {
Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()._deserializationContext())); Object value = reader.readValue(tokenBuffer.asParser(getMapper()._deserializationContext()));
logValue(value, hints); logValue(value, hints);
if (value != null) { if (value != null) {
sink.next(value); sink.next(value);
@ -189,7 +190,7 @@ public abstract class AbstractJacksonDecoder extends JacksonCodecSupport impleme
public Object decode(DataBuffer dataBuffer, ResolvableType targetType, public Object decode(DataBuffer dataBuffer, ResolvableType targetType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException { @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException {
ObjectMapper mapper = selectObjectMapper(targetType, mimeType); T mapper = selectMapper(targetType, mimeType);
if (mapper == null) { if (mapper == null) {
throw new IllegalStateException("No ObjectMapper for " + targetType); throw new IllegalStateException("No ObjectMapper for " + targetType);
} }
@ -208,8 +209,7 @@ public abstract class AbstractJacksonDecoder extends JacksonCodecSupport impleme
} }
} }
private ObjectReader createObjectReader( private ObjectReader createObjectReader(T mapper, ResolvableType elementType, @Nullable Map<String, Object> hints) {
ObjectMapper mapper, ResolvableType elementType, @Nullable Map<String, Object> hints) {
Assert.notNull(elementType, "'elementType' must not be null"); Assert.notNull(elementType, "'elementType' must not be null");
Class<?> contextClass = getContextClass(elementType); Class<?> contextClass = getContextClass(elementType);

15
spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java

@ -64,8 +64,9 @@ import org.springframework.util.MimeType;
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
* @param <T> the type of {@link ObjectMapper}
*/ */
public abstract class AbstractJacksonEncoder extends JacksonCodecSupport implements HttpMessageEncoder<Object> { public abstract class AbstractJacksonEncoder<T extends ObjectMapper> extends JacksonCodecSupport<T> implements HttpMessageEncoder<Object> {
private static final byte[] NEWLINE_SEPARATOR = {'\n'}; private static final byte[] NEWLINE_SEPARATOR = {'\n'};
@ -90,14 +91,14 @@ public abstract class AbstractJacksonEncoder extends JacksonCodecSupport impleme
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s. * by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s.
*/ */
protected AbstractJacksonEncoder(MapperBuilder<?, ?> builder, MimeType... mimeTypes) { protected AbstractJacksonEncoder(MapperBuilder<T, ?> builder, MimeType... mimeTypes) {
super(builder, mimeTypes); super(builder, mimeTypes);
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MimeType}s. * Construct a new instance with the provided {@link ObjectMapper} and {@link MimeType}s.
*/ */
protected AbstractJacksonEncoder(ObjectMapper mapper, MimeType... mimeTypes) { protected AbstractJacksonEncoder(T mapper, MimeType... mimeTypes) {
super(mapper, mimeTypes); super(mapper, mimeTypes);
} }
@ -122,7 +123,7 @@ public abstract class AbstractJacksonEncoder extends JacksonCodecSupport impleme
return false; return false;
} }
} }
if (this.objectMapperRegistrations != null && selectObjectMapper(elementType, mimeType) == null) { if (this.mapperRegistrations != null && selectMapper(elementType, mimeType) == null) {
return false; return false;
} }
Class<?> clazz = elementType.resolve(); Class<?> clazz = elementType.resolve();
@ -155,7 +156,7 @@ public abstract class AbstractJacksonEncoder extends JacksonCodecSupport impleme
} }
try { try {
ObjectMapper mapper = selectObjectMapper(elementType, mimeType); T mapper = selectMapper(elementType, mimeType);
if (mapper == null) { if (mapper == null) {
throw new IllegalStateException("No ObjectMapper for " + elementType); throw new IllegalStateException("No ObjectMapper for " + elementType);
} }
@ -225,7 +226,7 @@ public abstract class AbstractJacksonEncoder extends JacksonCodecSupport impleme
filters = (FilterProvider) hints.get(FILTER_PROVIDER_HINT); filters = (FilterProvider) hints.get(FILTER_PROVIDER_HINT);
} }
ObjectMapper mapper = selectObjectMapper(valueType, mimeType); T mapper = selectMapper(valueType, mimeType);
if (mapper == null) { if (mapper == null) {
throw new IllegalStateException("No ObjectMapper for " + valueType); throw new IllegalStateException("No ObjectMapper for " + valueType);
} }
@ -319,7 +320,7 @@ public abstract class AbstractJacksonEncoder extends JacksonCodecSupport impleme
} }
private ObjectWriter createObjectWriter( private ObjectWriter createObjectWriter(
ObjectMapper mapper, ResolvableType valueType, @Nullable MimeType mimeType, T mapper, ResolvableType valueType, @Nullable MimeType mimeType,
@Nullable Class<?> jsonView, @Nullable Map<String, Object> hints) { @Nullable Class<?> jsonView, @Nullable Map<String, Object> hints) {
JavaType javaType = getJavaType(valueType.getType(), null); JavaType javaType = getJavaType(valueType.getType(), null);

69
spring-web/src/main/java/org/springframework/http/codec/JacksonCodecSupport.java

@ -50,12 +50,13 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType; import org.springframework.util.MimeType;
/** /**
* Base class providing support methods for Jackson 2.x encoding and decoding. * Base class providing support methods for Jackson 3.x encoding and decoding.
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
* @param <T> the type of {@link ObjectMapper}
*/ */
public abstract class JacksonCodecSupport { public abstract class JacksonCodecSupport<T extends ObjectMapper> {
/** /**
* The key for the hint to specify a "JSON View" for encoding or decoding * The key for the hint to specify a "JSON View" for encoding or decoding
@ -83,9 +84,9 @@ public abstract class JacksonCodecSupport {
protected final Log logger = HttpLogging.forLogName(getClass()); protected final Log logger = HttpLogging.forLogName(getClass());
private final ObjectMapper defaultObjectMapper; private final T defaultMapper;
protected @Nullable Map<Class<?>, Map<MimeType, ObjectMapper>> objectMapperRegistrations; protected @Nullable Map<Class<?>, Map<MimeType, T>> mapperRegistrations;
private final List<MimeType> mimeTypes; private final List<MimeType> mimeTypes;
@ -96,10 +97,10 @@ public abstract class JacksonCodecSupport {
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s. * by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s.
*/ */
protected JacksonCodecSupport(MapperBuilder<?, ?> builder, MimeType... mimeTypes) { protected JacksonCodecSupport(MapperBuilder<T, ?> builder, MimeType... mimeTypes) {
Assert.notNull(builder, "MapperBuilder must not be null"); Assert.notNull(builder, "MapperBuilder must not be null");
Assert.notEmpty(mimeTypes, "MimeTypes must not be empty"); Assert.notEmpty(mimeTypes, "MimeTypes must not be empty");
this.defaultObjectMapper = builder.addModules(initModules()).build(); this.defaultMapper = builder.addModules(initModules()).build();
this.mimeTypes = List.of(mimeTypes); this.mimeTypes = List.of(mimeTypes);
} }
@ -108,10 +109,10 @@ public abstract class JacksonCodecSupport {
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s. * by {@link MapperBuilder#findModules(ClassLoader)} and {@link MimeType}s.
*/ */
protected JacksonCodecSupport(ObjectMapper objectMapper, MimeType... mimeTypes) { protected JacksonCodecSupport(T mapper, MimeType... mimeTypes) {
Assert.notNull(objectMapper, "ObjectMapper must not be null"); Assert.notNull(mapper, "ObjectMapper must not be null");
Assert.notEmpty(mimeTypes, "MimeTypes must not be empty"); Assert.notEmpty(mimeTypes, "MimeTypes must not be empty");
this.defaultObjectMapper = objectMapper; this.defaultMapper = mapper;
this.mimeTypes = List.of(mimeTypes); this.mimeTypes = List.of(mimeTypes);
} }
@ -124,19 +125,19 @@ public abstract class JacksonCodecSupport {
} }
/** /**
* Return the {@link ObjectMapper configured} default ObjectMapper. * Return the {@link ObjectMapper configured} default mapper.
*/ */
public ObjectMapper getObjectMapper() { public T getMapper() {
return this.defaultObjectMapper; return this.defaultMapper;
} }
/** /**
* Configure the {@link ObjectMapper} instances to use for the given * Configure the {@link ObjectMapper} instances to use for the given
* {@link Class}. This is useful when you want to deviate from the * {@link Class}. This is useful when you want to deviate from the
* {@link #getObjectMapper() default} ObjectMapper or have the * {@link #getMapper() default} ObjectMapper or have the
* {@code ObjectMapper} vary by {@code MediaType}. * {@code ObjectMapper} vary by {@code MediaType}.
* <p><strong>Note:</strong> Use of this method effectively turns off use of * <p><strong>Note:</strong> Use of this method effectively turns off use of
* the default {@link #getObjectMapper() ObjectMapper} and supported * the default {@link #getMapper() ObjectMapper} and supported
* {@link #getMimeTypes() MimeTypes} for the given class. Therefore it is * {@link #getMimeTypes() MimeTypes} for the given class. Therefore it is
* important for the mappings configured here to * important for the mappings configured here to
* {@link MediaType#includes(MediaType) include} every MediaType that must * {@link MediaType#includes(MediaType) include} every MediaType that must
@ -145,12 +146,12 @@ public abstract class JacksonCodecSupport {
* @param registrar a consumer to populate or otherwise update the * @param registrar a consumer to populate or otherwise update the
* MediaType-to-ObjectMapper associations for the given Class * MediaType-to-ObjectMapper associations for the given Class
*/ */
public void registerObjectMappersForType(Class<?> clazz, Consumer<Map<MimeType, ObjectMapper>> registrar) { public void registerMappersForType(Class<?> clazz, Consumer<Map<MimeType, T>> registrar) {
if (this.objectMapperRegistrations == null) { if (this.mapperRegistrations == null) {
this.objectMapperRegistrations = new LinkedHashMap<>(); this.mapperRegistrations = new LinkedHashMap<>();
} }
Map<MimeType, ObjectMapper> registrations = Map<MimeType, T> registrations =
this.objectMapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>()); this.mapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>());
registrar.accept(registrations); registrar.accept(registrations);
} }
@ -160,8 +161,8 @@ public abstract class JacksonCodecSupport {
* @return a map with registered MediaType-to-ObjectMapper registrations, * @return a map with registered MediaType-to-ObjectMapper registrations,
* or empty if in case of no registrations for the given class. * or empty if in case of no registrations for the given class.
*/ */
public @Nullable Map<MimeType, ObjectMapper> getObjectMappersForType(Class<?> clazz) { public @Nullable Map<MimeType, T> getMappersForType(Class<?> clazz) {
for (Map.Entry<Class<?>, Map<MimeType, ObjectMapper>> entry : getObjectMapperRegistrations().entrySet()) { for (Map.Entry<Class<?>, Map<MimeType, T>> entry : getMapperRegistrations().entrySet()) {
if (entry.getKey().isAssignableFrom(clazz)) { if (entry.getKey().isAssignableFrom(clazz)) {
return entry.getValue(); return entry.getValue();
} }
@ -169,8 +170,8 @@ public abstract class JacksonCodecSupport {
return Collections.emptyMap(); return Collections.emptyMap();
} }
protected Map<Class<?>, Map<MimeType, ObjectMapper>> getObjectMapperRegistrations() { protected Map<Class<?>, Map<MimeType, T>> getMapperRegistrations() {
return (this.objectMapperRegistrations != null ? this.objectMapperRegistrations : Collections.emptyMap()); return (this.mapperRegistrations != null ? this.mapperRegistrations : Collections.emptyMap());
} }
/** /**
@ -183,7 +184,7 @@ public abstract class JacksonCodecSupport {
protected List<MimeType> getMimeTypes(ResolvableType elementType) { protected List<MimeType> getMimeTypes(ResolvableType elementType) {
Class<?> elementClass = elementType.toClass(); Class<?> elementClass = elementType.toClass();
List<MimeType> result = null; List<MimeType> result = null;
for (Map.Entry<Class<?>, Map<MimeType, ObjectMapper>> entry : getObjectMapperRegistrations().entrySet()) { for (Map.Entry<Class<?>, Map<MimeType, T>> entry : getMapperRegistrations().entrySet()) {
if (entry.getKey().isAssignableFrom(elementClass)) { if (entry.getKey().isAssignableFrom(elementClass)) {
result = (result != null ? result : new ArrayList<>(entry.getValue().size())); result = (result != null ? result : new ArrayList<>(entry.getValue().size()));
result.addAll(entry.getValue().keySet()); result.addAll(entry.getValue().keySet());
@ -216,7 +217,7 @@ public abstract class JacksonCodecSupport {
} }
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) { protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
return this.defaultObjectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass)); return this.defaultMapper.constructType(GenericTypeResolver.resolveType(type, contextClass));
} }
protected Map<String, Object> getHints(ResolvableType resolvableType) { protected Map<String, Object> getHints(ResolvableType resolvableType) {
@ -250,18 +251,18 @@ public abstract class JacksonCodecSupport {
/** /**
* Select an ObjectMapper to use, either the main ObjectMapper or another * Select an ObjectMapper to use, either the main ObjectMapper or another
* if the handling for the given Class has been customized through * if the handling for the given Class has been customized through
* {@link #registerObjectMappersForType(Class, Consumer)}. * {@link #registerMappersForType(Class, Consumer)}.
*/ */
protected @Nullable ObjectMapper selectObjectMapper(ResolvableType targetType, @Nullable MimeType targetMimeType) { protected @Nullable T selectMapper(ResolvableType targetType, @Nullable MimeType targetMimeType) {
if (targetMimeType == null || CollectionUtils.isEmpty(this.objectMapperRegistrations)) { if (targetMimeType == null || CollectionUtils.isEmpty(this.mapperRegistrations)) {
return this.defaultObjectMapper; return this.defaultMapper;
} }
Class<?> targetClass = targetType.toClass(); Class<?> targetClass = targetType.toClass();
for (Map.Entry<Class<?>, Map<MimeType, ObjectMapper>> typeEntry : getObjectMapperRegistrations().entrySet()) { for (Map.Entry<Class<?>, Map<MimeType, T>> typeEntry : getMapperRegistrations().entrySet()) {
if (typeEntry.getKey().isAssignableFrom(targetClass)) { if (typeEntry.getKey().isAssignableFrom(targetClass)) {
for (Map.Entry<MimeType, ObjectMapper> objectMapperEntry : typeEntry.getValue().entrySet()) { for (Map.Entry<MimeType, T> mapperEntry : typeEntry.getValue().entrySet()) {
if (objectMapperEntry.getKey().includes(targetMimeType)) { if (mapperEntry.getKey().includes(targetMimeType)) {
return objectMapperEntry.getValue(); return mapperEntry.getValue();
} }
} }
// No matching registrations // No matching registrations
@ -269,7 +270,7 @@ public abstract class JacksonCodecSupport {
} }
} }
// No registrations // No registrations
return this.defaultObjectMapper; return this.defaultMapper;
} }
} }

2
spring-web/src/main/java/org/springframework/http/codec/cbor/JacksonCborDecoder.java

@ -40,7 +40,7 @@ import org.springframework.util.MimeType;
* @see JacksonCborEncoder * @see JacksonCborEncoder
* @see <a href="https://github.com/spring-projects/spring-framework/issues/20513">Add CBOR support to WebFlux</a> * @see <a href="https://github.com/spring-projects/spring-framework/issues/20513">Add CBOR support to WebFlux</a>
*/ */
public class JacksonCborDecoder extends AbstractJacksonDecoder { public class JacksonCborDecoder extends AbstractJacksonDecoder<CBORMapper> {
/** /**
* Construct a new instance with a {@link CBORMapper} customized with the * Construct a new instance with a {@link CBORMapper} customized with the

2
spring-web/src/main/java/org/springframework/http/codec/cbor/JacksonCborEncoder.java

@ -41,7 +41,7 @@ import org.springframework.util.MimeType;
* @see JacksonCborDecoder * @see JacksonCborDecoder
* @see <a href="https://github.com/spring-projects/spring-framework/issues/20513">Add CBOR support to WebFlux</a> * @see <a href="https://github.com/spring-projects/spring-framework/issues/20513">Add CBOR support to WebFlux</a>
*/ */
public class JacksonCborEncoder extends AbstractJacksonEncoder { public class JacksonCborEncoder extends AbstractJacksonEncoder<CBORMapper> {
/** /**
* Construct a new instance with a {@link CBORMapper} customized with the * Construct a new instance with a {@link CBORMapper} customized with the

11
spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java

@ -25,7 +25,6 @@ import java.util.Map;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -50,7 +49,7 @@ import org.springframework.util.MimeTypeUtils;
* @since 7.0 * @since 7.0
* @see JacksonJsonEncoder * @see JacksonJsonEncoder
*/ */
public class JacksonJsonDecoder extends AbstractJacksonDecoder { public class JacksonJsonDecoder extends AbstractJacksonDecoder<JsonMapper> {
private static final CharBufferDecoder CHAR_BUFFER_DECODER = CharBufferDecoder.textPlainOnly(Arrays.asList(",", "\n"), false); private static final CharBufferDecoder CHAR_BUFFER_DECODER = CharBufferDecoder.textPlainOnly(Arrays.asList(",", "\n"), false);
@ -73,20 +72,20 @@ public class JacksonJsonDecoder extends AbstractJacksonDecoder {
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link JsonMapper}.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonDecoder(ObjectMapper mapper) { public JacksonJsonDecoder(JsonMapper mapper) {
this(mapper, DEFAULT_JSON_MIME_TYPES); this(mapper, DEFAULT_JSON_MIME_TYPES);
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MimeType}s. * Construct a new instance with the provided {@link JsonMapper} and {@link MimeType}s.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonDecoder(ObjectMapper mapper, MimeType... mimeTypes) { public JacksonJsonDecoder(JsonMapper mapper, MimeType... mimeTypes) {
super(mapper, mimeTypes); super(mapper, mimeTypes);
} }

11
spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonEncoder.java

@ -25,7 +25,6 @@ import reactor.core.publisher.Flux;
import tools.jackson.core.PrettyPrinter; import tools.jackson.core.PrettyPrinter;
import tools.jackson.core.util.DefaultIndenter; import tools.jackson.core.util.DefaultIndenter;
import tools.jackson.core.util.DefaultPrettyPrinter; import tools.jackson.core.util.DefaultPrettyPrinter;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectWriter; import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
@ -51,7 +50,7 @@ import org.springframework.util.MimeType;
* @since 7.0 * @since 7.0
* @see JacksonJsonDecoder * @see JacksonJsonDecoder
*/ */
public class JacksonJsonEncoder extends AbstractJacksonEncoder { public class JacksonJsonEncoder extends AbstractJacksonEncoder<JsonMapper> {
private static final List<MimeType> problemDetailMimeTypes = private static final List<MimeType> problemDetailMimeTypes =
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON); Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
@ -80,21 +79,21 @@ public class JacksonJsonEncoder extends AbstractJacksonEncoder {
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link JsonMapper}.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonEncoder(ObjectMapper mapper) { public JacksonJsonEncoder(JsonMapper mapper) {
this(mapper, DEFAULT_JSON_MIME_TYPES); this(mapper, DEFAULT_JSON_MIME_TYPES);
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and * Construct a new instance with the provided {@link JsonMapper} and
* {@link MimeType}s. * {@link MimeType}s.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonEncoder(ObjectMapper mapper, MimeType... mimeTypes) { public JacksonJsonEncoder(JsonMapper mapper, MimeType... mimeTypes) {
super(mapper, mimeTypes); super(mapper, mimeTypes);
setStreamingMediaTypes(List.of(MediaType.APPLICATION_NDJSON)); setStreamingMediaTypes(List.of(MediaType.APPLICATION_NDJSON));
this.ssePrettyPrinter = initSsePrettyPrinter(); this.ssePrettyPrinter = initSsePrettyPrinter();

2
spring-web/src/main/java/org/springframework/http/codec/smile/JacksonSmileDecoder.java

@ -33,7 +33,7 @@ import org.springframework.util.MimeType;
* @since 7.0 * @since 7.0
* @see JacksonSmileEncoder * @see JacksonSmileEncoder
*/ */
public class JacksonSmileDecoder extends AbstractJacksonDecoder { public class JacksonSmileDecoder extends AbstractJacksonDecoder<SmileMapper> {
private static final MimeType[] DEFAULT_SMILE_MIME_TYPES = new MimeType[] { private static final MimeType[] DEFAULT_SMILE_MIME_TYPES = new MimeType[] {
new MimeType("application", "x-jackson-smile"), new MimeType("application", "x-jackson-smile"),

2
spring-web/src/main/java/org/springframework/http/codec/smile/JacksonSmileEncoder.java

@ -41,7 +41,7 @@ import org.springframework.util.MimeType;
* @since 7.0 * @since 7.0
* @see JacksonSmileDecoder * @see JacksonSmileDecoder
*/ */
public class JacksonSmileEncoder extends AbstractJacksonEncoder { public class JacksonSmileEncoder extends AbstractJacksonEncoder<SmileMapper> {
private static final MimeType[] DEFAULT_SMILE_MIME_TYPES = new MimeType[] { private static final MimeType[] DEFAULT_SMILE_MIME_TYPES = new MimeType[] {
new MimeType("application", "x-jackson-smile"), new MimeType("application", "x-jackson-smile"),

2
spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java

@ -527,7 +527,7 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
} }
} }
if (jacksonPresent) { if (jacksonPresent) {
if (codec instanceof AbstractJacksonDecoder abstractJacksonDecoder) { if (codec instanceof AbstractJacksonDecoder<?> abstractJacksonDecoder) {
abstractJacksonDecoder.setMaxInMemorySize(size); abstractJacksonDecoder.setMaxInMemorySize(size);
} }
} }

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

@ -82,9 +82,10 @@ import org.springframework.util.TypeUtils;
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
* @param <T> the type of {@link ObjectMapper}
* @see JacksonJsonHttpMessageConverter * @see JacksonJsonHttpMessageConverter
*/ */
public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartHttpMessageConverter<Object> { public abstract class AbstractJacksonHttpMessageConverter<T extends ObjectMapper> extends AbstractSmartHttpMessageConverter<Object> {
private static final String JSON_VIEW_HINT = JsonView.class.getName(); private static final String JSON_VIEW_HINT = JsonView.class.getName();
@ -103,9 +104,9 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
} }
protected final ObjectMapper defaultObjectMapper; protected final T defaultMapper;
private @Nullable Map<Class<?>, Map<MediaType, ObjectMapper>> objectMapperRegistrations; private @Nullable Map<Class<?>, Map<MediaType, T>> mapperRegistrations;
private final @Nullable PrettyPrinter ssePrettyPrinter; private final @Nullable PrettyPrinter ssePrettyPrinter;
@ -115,8 +116,8 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)}. * by {@link MapperBuilder#findModules(ClassLoader)}.
*/ */
private AbstractJacksonHttpMessageConverter(MapperBuilder<?, ?> builder) { private AbstractJacksonHttpMessageConverter(MapperBuilder<T, ?> builder) {
this.defaultObjectMapper = builder.addModules(initModules()).build(); this.defaultMapper = builder.addModules(initModules()).build();
this.ssePrettyPrinter = initSsePrettyPrinter(); this.ssePrettyPrinter = initSsePrettyPrinter();
} }
@ -125,7 +126,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MediaType}. * by {@link MapperBuilder#findModules(ClassLoader)} and {@link MediaType}.
*/ */
protected AbstractJacksonHttpMessageConverter(MapperBuilder<?, ?> builder, MediaType supportedMediaType) { protected AbstractJacksonHttpMessageConverter(MapperBuilder<T, ?> builder, MediaType supportedMediaType) {
this(builder); this(builder);
setSupportedMediaTypes(Collections.singletonList(supportedMediaType)); setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
} }
@ -135,7 +136,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
* customized with the {@link tools.jackson.databind.JacksonModule}s found * customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)} and {@link MediaType}s. * by {@link MapperBuilder#findModules(ClassLoader)} and {@link MediaType}s.
*/ */
protected AbstractJacksonHttpMessageConverter(MapperBuilder<?, ?> builder, MediaType... supportedMediaTypes) { protected AbstractJacksonHttpMessageConverter(MapperBuilder<T, ?> builder, MediaType... supportedMediaTypes) {
this(builder); this(builder);
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes)); setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
} }
@ -143,24 +144,24 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link ObjectMapper}.
*/ */
protected AbstractJacksonHttpMessageConverter(ObjectMapper objectMapper) { protected AbstractJacksonHttpMessageConverter(T mapper) {
this.defaultObjectMapper = objectMapper; this.defaultMapper = mapper;
this.ssePrettyPrinter = initSsePrettyPrinter(); this.ssePrettyPrinter = initSsePrettyPrinter();
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MediaType}. * Construct a new instance with the provided {@link ObjectMapper} and {@link MediaType}.
*/ */
protected AbstractJacksonHttpMessageConverter(ObjectMapper objectMapper, MediaType supportedMediaType) { protected AbstractJacksonHttpMessageConverter(T mapper, MediaType supportedMediaType) {
this(objectMapper); this(mapper);
setSupportedMediaTypes(Collections.singletonList(supportedMediaType)); setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper} and {@link MediaType}s. * Construct a new instance with the provided {@link ObjectMapper} and {@link MediaType}s.
*/ */
protected AbstractJacksonHttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes) { protected AbstractJacksonHttpMessageConverter(T mapper, MediaType... supportedMediaTypes) {
this(objectMapper); this(mapper);
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes)); setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
} }
@ -184,19 +185,19 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
} }
/** /**
* Return the main {@code ObjectMapper} in use. * Return the main {@link ObjectMapper} in use.
*/ */
public ObjectMapper getObjectMapper() { public T getMapper() {
return this.defaultObjectMapper; return this.defaultMapper;
} }
/** /**
* Configure the {@link ObjectMapper} instances to use for the given * Configure the {@link ObjectMapper} instances to use for the given
* {@link Class}. This is useful when you want to deviate from the * {@link Class}. This is useful when you want to deviate from the
* {@link #getObjectMapper() default} ObjectMapper or have the * {@link #getMapper() default} ObjectMapper or have the
* {@code ObjectMapper} vary by {@code MediaType}. * {@code ObjectMapper} vary by {@code MediaType}.
* <p><strong>Note:</strong> Use of this method effectively turns off use of * <p><strong>Note:</strong> Use of this method effectively turns off use of
* the default {@link #getObjectMapper() ObjectMapper} and * the default {@link #getMapper() ObjectMapper} and
* {@link #setSupportedMediaTypes(List) supportedMediaTypes} for the given * {@link #setSupportedMediaTypes(List) supportedMediaTypes} for the given
* class. Therefore it is important for the mappings configured here to * class. Therefore it is important for the mappings configured here to
* {@link MediaType#includes(MediaType) include} every MediaType that must * {@link MediaType#includes(MediaType) include} every MediaType that must
@ -205,12 +206,12 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
* @param registrar a consumer to populate or otherwise update the * @param registrar a consumer to populate or otherwise update the
* MediaType-to-ObjectMapper associations for the given Class * MediaType-to-ObjectMapper associations for the given Class
*/ */
public void registerObjectMappersForType(Class<?> clazz, Consumer<Map<MediaType, ObjectMapper>> registrar) { public void registerMappersForType(Class<?> clazz, Consumer<Map<MediaType, T>> registrar) {
if (this.objectMapperRegistrations == null) { if (this.mapperRegistrations == null) {
this.objectMapperRegistrations = new LinkedHashMap<>(); this.mapperRegistrations = new LinkedHashMap<>();
} }
Map<MediaType, ObjectMapper> registrations = Map<MediaType, T> registrations =
this.objectMapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>()); this.mapperRegistrations.computeIfAbsent(clazz, c -> new LinkedHashMap<>());
registrar.accept(registrations); registrar.accept(registrations);
} }
@ -220,8 +221,8 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
* @return a map with registered MediaType-to-ObjectMapper registrations, * @return a map with registered MediaType-to-ObjectMapper registrations,
* or empty if in case of no registrations for the given class. * or empty if in case of no registrations for the given class.
*/ */
public Map<MediaType, ObjectMapper> getObjectMappersForType(Class<?> clazz) { public Map<MediaType, T> getMappersForType(Class<?> clazz) {
for (Map.Entry<Class<?>, Map<MediaType, ObjectMapper>> entry : getObjectMapperRegistrations().entrySet()) { for (Map.Entry<Class<?>, Map<MediaType, T>> entry : getMapperRegistrations().entrySet()) {
if (entry.getKey().isAssignableFrom(clazz)) { if (entry.getKey().isAssignableFrom(clazz)) {
return entry.getValue(); return entry.getValue();
} }
@ -232,7 +233,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
@Override @Override
public List<MediaType> getSupportedMediaTypes(Class<?> clazz) { public List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
List<MediaType> result = null; List<MediaType> result = null;
for (Map.Entry<Class<?>, Map<MediaType, ObjectMapper>> entry : getObjectMapperRegistrations().entrySet()) { for (Map.Entry<Class<?>, Map<MediaType, T>> entry : getMapperRegistrations().entrySet()) {
if (entry.getKey().isAssignableFrom(clazz)) { if (entry.getKey().isAssignableFrom(clazz)) {
result = (result != null ? result : new ArrayList<>(entry.getValue().size())); result = (result != null ? result : new ArrayList<>(entry.getValue().size()));
result.addAll(entry.getValue().keySet()); result.addAll(entry.getValue().keySet());
@ -245,8 +246,8 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
getMediaTypesForProblemDetail() : getSupportedMediaTypes()); getMediaTypesForProblemDetail() : getSupportedMediaTypes());
} }
private Map<Class<?>, Map<MediaType, ObjectMapper>> getObjectMapperRegistrations() { private Map<Class<?>, Map<MediaType, T>> getMapperRegistrations() {
return (this.objectMapperRegistrations != null ? this.objectMapperRegistrations : Collections.emptyMap()); return (this.mapperRegistrations != null ? this.mapperRegistrations : Collections.emptyMap());
} }
/** /**
@ -267,7 +268,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
if (clazz == null) { if (clazz == null) {
return false; return false;
} }
return this.objectMapperRegistrations == null || selectObjectMapper(clazz, mediaType) != null; return this.mapperRegistrations == null || selectMapper(clazz, mediaType) != null;
} }
@Override @Override
@ -285,23 +286,23 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
if (MappingJacksonValue.class.isAssignableFrom(clazz)) { if (MappingJacksonValue.class.isAssignableFrom(clazz)) {
throw new UnsupportedOperationException("MappingJacksonValue is not supported, use hints instead"); throw new UnsupportedOperationException("MappingJacksonValue is not supported, use hints instead");
} }
return this.objectMapperRegistrations == null || selectObjectMapper(clazz, mediaType) != null; return this.mapperRegistrations == null || selectMapper(clazz, mediaType) != null;
} }
/** /**
* Select an ObjectMapper to use, either the main ObjectMapper or another * Select an ObjectMapper to use, either the main ObjectMapper or another
* if the handling for the given Class has been customized through * if the handling for the given Class has been customized through
* {@link #registerObjectMappersForType(Class, Consumer)}. * {@link #registerMappersForType(Class, Consumer)}.
*/ */
private @Nullable ObjectMapper selectObjectMapper(Class<?> targetType, @Nullable MediaType targetMediaType) { private @Nullable T selectMapper(Class<?> targetType, @Nullable MediaType targetMediaType) {
if (targetMediaType == null || CollectionUtils.isEmpty(this.objectMapperRegistrations)) { if (targetMediaType == null || CollectionUtils.isEmpty(this.mapperRegistrations)) {
return this.defaultObjectMapper; return this.defaultMapper;
} }
for (Map.Entry<Class<?>, Map<MediaType, ObjectMapper>> typeEntry : getObjectMapperRegistrations().entrySet()) { for (Map.Entry<Class<?>, Map<MediaType, T>> typeEntry : getMapperRegistrations().entrySet()) {
if (typeEntry.getKey().isAssignableFrom(targetType)) { if (typeEntry.getKey().isAssignableFrom(targetType)) {
for (Map.Entry<MediaType, ObjectMapper> objectMapperEntry : typeEntry.getValue().entrySet()) { for (Map.Entry<MediaType, T> mapperEntry : typeEntry.getValue().entrySet()) {
if (objectMapperEntry.getKey().includes(targetMediaType)) { if (mapperEntry.getKey().includes(targetMediaType)) {
return objectMapperEntry.getValue(); return mapperEntry.getValue();
} }
} }
// No matching registrations // No matching registrations
@ -309,7 +310,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
} }
} }
// No registrations // No registrations
return this.defaultObjectMapper; return this.defaultMapper;
} }
@Override @Override
@ -334,8 +335,8 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
MediaType contentType = inputMessage.getHeaders().getContentType(); MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = getCharset(contentType); Charset charset = getCharset(contentType);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType); T mapper = selectMapper(javaType.getRawClass(), contentType);
Assert.state(objectMapper != null, () -> "No ObjectMapper for " + javaType); Assert.state(mapper != null, () -> "No ObjectMapper for " + javaType);
boolean isUnicode = ENCODINGS.containsKey(charset.name()) || boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
"UTF-16".equals(charset.name()) || "UTF-16".equals(charset.name()) ||
@ -345,7 +346,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
if (inputMessage instanceof MappingJacksonInputMessage) { if (inputMessage instanceof MappingJacksonInputMessage) {
throw new UnsupportedOperationException("MappingJacksonInputMessage is not supported, use hints instead"); throw new UnsupportedOperationException("MappingJacksonInputMessage is not supported, use hints instead");
} }
ObjectReader objectReader = objectMapper.readerFor(javaType); ObjectReader objectReader = mapper.readerFor(javaType);
if (hints != null && hints.containsKey(JSON_VIEW_HINT)) { if (hints != null && hints.containsKey(JSON_VIEW_HINT)) {
objectReader = objectReader.withView((Class<?>) hints.get(JSON_VIEW_HINT)); objectReader = objectReader.withView((Class<?>) hints.get(JSON_VIEW_HINT));
} }
@ -401,8 +402,8 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
JsonEncoding encoding = getJsonEncoding(contentType); JsonEncoding encoding = getJsonEncoding(contentType);
Class<?> clazz = object.getClass(); Class<?> clazz = object.getClass();
ObjectMapper objectMapper = selectObjectMapper(clazz, contentType); T mapper = selectMapper(clazz, contentType);
Assert.state(objectMapper != null, () -> "No ObjectMapper for " + clazz.getName()); Assert.state(mapper != null, () -> "No ObjectMapper for " + clazz.getName());
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody()); OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
Class<?> jsonView = null; Class<?> jsonView = null;
@ -419,7 +420,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
} }
ObjectWriter objectWriter = (jsonView != null ? ObjectWriter objectWriter = (jsonView != null ?
objectMapper.writerWithView(jsonView) : objectMapper.writer()); mapper.writerWithView(jsonView) : mapper.writer());
if (filters != null) { if (filters != null) {
objectWriter = objectWriter.with(filters); objectWriter = objectWriter.with(filters);
} }
@ -485,7 +486,7 @@ public abstract class AbstractJacksonHttpMessageConverter extends AbstractSmartH
* @return the Jackson JavaType * @return the Jackson JavaType
*/ */
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) { protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
return this.defaultObjectMapper.constructType(GenericTypeResolver.resolveType(type, contextClass)); return this.defaultMapper.constructType(GenericTypeResolver.resolveType(type, contextClass));
} }
/** /**

2
spring-web/src/main/java/org/springframework/http/converter/cbor/JacksonCborHttpMessageConverter.java

@ -38,7 +38,7 @@ import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
*/ */
public class JacksonCborHttpMessageConverter extends AbstractJacksonHttpMessageConverter { public class JacksonCborHttpMessageConverter extends AbstractJacksonHttpMessageConverter<CBORMapper> {
/** /**
* Construct a new instance with a {@link CBORMapper} customized with the * Construct a new instance with a {@link CBORMapper} customized with the

9
spring-web/src/main/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverter.java

@ -21,7 +21,6 @@ import java.util.List;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -32,7 +31,7 @@ import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
/** /**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} * 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> * that can read and write JSON using <a href="https://github.com/FasterXML/jackson">Jackson 3.x's</a>
* {@link ObjectMapper}. * {@link JsonMapper}.
* *
* <p>This converter can be used to bind to typed beans, or untyped * <p>This converter can be used to bind to typed beans, or untyped
* {@code HashMap} instances. * {@code HashMap} instances.
@ -56,7 +55,7 @@ import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
*/ */
public class JacksonJsonHttpMessageConverter extends AbstractJacksonHttpMessageConverter { public class JacksonJsonHttpMessageConverter extends AbstractJacksonHttpMessageConverter<JsonMapper> {
private static final List<MediaType> problemDetailMediaTypes = private static final List<MediaType> problemDetailMediaTypes =
Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON); Collections.singletonList(MediaType.APPLICATION_PROBLEM_JSON);
@ -79,11 +78,11 @@ public class JacksonJsonHttpMessageConverter extends AbstractJacksonHttpMessageC
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link JsonMapper}.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findModules(ClassLoader) * @see MapperBuilder#findModules(ClassLoader)
*/ */
public JacksonJsonHttpMessageConverter(ObjectMapper objectMapper) { public JacksonJsonHttpMessageConverter(JsonMapper objectMapper) {
super(objectMapper, DEFAULT_JSON_MIME_TYPES); super(objectMapper, DEFAULT_JSON_MIME_TYPES);
} }

2
spring-web/src/main/java/org/springframework/http/converter/smile/JacksonSmileHttpMessageConverter.java

@ -38,7 +38,7 @@ import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
*/ */
public class JacksonSmileHttpMessageConverter extends AbstractJacksonHttpMessageConverter { public class JacksonSmileHttpMessageConverter extends AbstractJacksonHttpMessageConverter<SmileMapper> {
private static final MediaType DEFAULT_SMILE_MIME_TYPES = new MediaType("application", "x-jackson-smile"); private static final MediaType DEFAULT_SMILE_MIME_TYPES = new MediaType("application", "x-jackson-smile");

2
spring-web/src/main/java/org/springframework/http/converter/xml/JacksonXmlHttpMessageConverter.java

@ -53,7 +53,7 @@ import org.springframework.util.xml.StaxUtils;
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
*/ */
public class JacksonXmlHttpMessageConverter extends AbstractJacksonHttpMessageConverter { public class JacksonXmlHttpMessageConverter extends AbstractJacksonHttpMessageConverter<XmlMapper> {
private static final List<MediaType> problemDetailMediaTypes = private static final List<MediaType> problemDetailMediaTypes =
Collections.singletonList(MediaType.APPLICATION_PROBLEM_XML); Collections.singletonList(MediaType.APPLICATION_PROBLEM_XML);

2
spring-web/src/main/java/org/springframework/http/converter/yaml/JacksonYamlHttpMessageConverter.java

@ -38,7 +38,7 @@ import org.springframework.http.converter.AbstractJacksonHttpMessageConverter;
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 7.0 * @since 7.0
*/ */
public class JacksonYamlHttpMessageConverter extends AbstractJacksonHttpMessageConverter { public class JacksonYamlHttpMessageConverter extends AbstractJacksonHttpMessageConverter<YAMLMapper> {
/** /**
* Construct a new instance with a {@link YAMLMapper} customized with the * Construct a new instance with a {@link YAMLMapper} customized with the

15
spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonDecoderTests.java

@ -31,7 +31,6 @@ import reactor.test.StepVerifier;
import tools.jackson.core.JsonParser; import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext; import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode; import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.annotation.JsonDeserialize; import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.deser.std.StdDeserializer; import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -101,9 +100,9 @@ class JacksonJsonDecoderTests extends AbstractDecoderTests<JacksonJsonDecoder> {
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), halFormsJsonMediaType)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), halFormsJsonMediaType)).isTrue();
assertThat(decoder.canDecode(ResolvableType.forClass(Map.class), MediaType.APPLICATION_JSON)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Map.class), MediaType.APPLICATION_JSON)).isTrue();
decoder.registerObjectMappersForType(Pojo.class, map -> { decoder.registerMappersForType(Pojo.class, map -> {
map.put(halJsonMediaType, new ObjectMapper()); map.put(halJsonMediaType, new JsonMapper());
map.put(MediaType.APPLICATION_JSON, new ObjectMapper()); map.put(MediaType.APPLICATION_JSON, new JsonMapper());
}); });
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), halJsonMediaType)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), halJsonMediaType)).isTrue();
@ -115,7 +114,7 @@ class JacksonJsonDecoderTests extends AbstractDecoderTests<JacksonJsonDecoder> {
@Test // SPR-15866 @Test // SPR-15866
void canDecodeWithProvidedMimeType() { void canDecodeWithProvidedMimeType() {
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
JacksonJsonDecoder decoder = new JacksonJsonDecoder(new ObjectMapper(), textJavascript); JacksonJsonDecoder decoder = new JacksonJsonDecoder(new JsonMapper(), textJavascript);
assertThat(decoder.getDecodableMimeTypes()).isEqualTo(Collections.singletonList(textJavascript)); assertThat(decoder.getDecodableMimeTypes()).isEqualTo(Collections.singletonList(textJavascript));
} }
@ -124,7 +123,7 @@ class JacksonJsonDecoderTests extends AbstractDecoderTests<JacksonJsonDecoder> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void decodableMimeTypesIsImmutable() { void decodableMimeTypesIsImmutable() {
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
JacksonJsonDecoder decoder = new JacksonJsonDecoder(new ObjectMapper(), textJavascript); JacksonJsonDecoder decoder = new JacksonJsonDecoder(new JsonMapper(), textJavascript);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
decoder.getDecodableMimeTypes().add(new MimeType("text", "ecmascript"))); decoder.getDecodableMimeTypes().add(new MimeType("text", "ecmascript")));
@ -135,8 +134,8 @@ class JacksonJsonDecoderTests extends AbstractDecoderTests<JacksonJsonDecoder> {
MimeType mimeType1 = MediaType.parseMediaType("application/hal+json"); MimeType mimeType1 = MediaType.parseMediaType("application/hal+json");
MimeType mimeType2 = new MimeType("text", "javascript", StandardCharsets.UTF_8); MimeType mimeType2 = new MimeType("text", "javascript", StandardCharsets.UTF_8);
JacksonJsonDecoder decoder = new JacksonJsonDecoder(new ObjectMapper(), mimeType2); JacksonJsonDecoder decoder = new JacksonJsonDecoder(new JsonMapper(), mimeType2);
decoder.registerObjectMappersForType(Pojo.class, map -> map.put(mimeType1, new ObjectMapper())); decoder.registerMappersForType(Pojo.class, map -> map.put(mimeType1, new JsonMapper()));
assertThat(decoder.getDecodableMimeTypes(ResolvableType.forClass(Pojo.class))) assertThat(decoder.getDecodableMimeTypes(ResolvableType.forClass(Pojo.class)))
.containsExactly(mimeType1); .containsExactly(mimeType1);

7
spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonEncoderTests.java

@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -109,7 +108,7 @@ class JacksonJsonEncoderTests extends AbstractEncoderTests<JacksonJsonEncoder> {
@Test // SPR-15866 @Test // SPR-15866
public void canEncodeWithCustomMimeType() { public void canEncodeWithCustomMimeType() {
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
JacksonJsonEncoder encoder = new JacksonJsonEncoder(new ObjectMapper(), textJavascript); JacksonJsonEncoder encoder = new JacksonJsonEncoder(new JsonMapper(), textJavascript);
assertThat(encoder.getEncodableMimeTypes()).isEqualTo(Collections.singletonList(textJavascript)); assertThat(encoder.getEncodableMimeTypes()).isEqualTo(Collections.singletonList(textJavascript));
} }
@ -117,7 +116,7 @@ class JacksonJsonEncoderTests extends AbstractEncoderTests<JacksonJsonEncoder> {
@Test @Test
void encodableMimeTypesIsImmutable() { void encodableMimeTypesIsImmutable() {
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8); MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
JacksonJsonEncoder encoder = new JacksonJsonEncoder(new ObjectMapper(), textJavascript); JacksonJsonEncoder encoder = new JacksonJsonEncoder(new JsonMapper(), textJavascript);
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
encoder.getEncodableMimeTypes().add(new MimeType("text", "ecmascript"))); encoder.getEncodableMimeTypes().add(new MimeType("text", "ecmascript")));
@ -231,7 +230,7 @@ class JacksonJsonEncoderTests extends AbstractEncoderTests<JacksonJsonEncoder> {
@Test // gh-22771 @Test // gh-22771
public void encodeWithFlushAfterWriteOff() { public void encodeWithFlushAfterWriteOff() {
ObjectMapper mapper = JsonMapper.builder().configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false).build(); JsonMapper mapper = JsonMapper.builder().configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false).build();
JacksonJsonEncoder encoder = new JacksonJsonEncoder(mapper); JacksonJsonEncoder encoder = new JacksonJsonEncoder(mapper);
Flux<DataBuffer> result = encoder.encode(Flux.just(new Pojo("foo", "bar")), this.bufferFactory, Flux<DataBuffer> result = encoder.encode(Flux.just(new Pojo("foo", "bar")), this.bufferFactory,

16
spring-web/src/test/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverterTests.java

@ -87,9 +87,9 @@ class JacksonJsonHttpMessageConverterTests {
assertThat(converter.canRead(MyBean.class, halFormsJsonMediaType)).isTrue(); assertThat(converter.canRead(MyBean.class, halFormsJsonMediaType)).isTrue();
assertThat(converter.canRead(Map.class, MediaType.APPLICATION_JSON)).isTrue(); assertThat(converter.canRead(Map.class, MediaType.APPLICATION_JSON)).isTrue();
converter.registerObjectMappersForType(MyBean.class, map -> { converter.registerMappersForType(MyBean.class, map -> {
map.put(halJsonMediaType, new ObjectMapper()); map.put(halJsonMediaType, new JsonMapper());
map.put(MediaType.APPLICATION_JSON, new ObjectMapper()); map.put(MediaType.APPLICATION_JSON, new JsonMapper());
}); });
assertThat(converter.canRead(MyBean.class, halJsonMediaType)).isTrue(); assertThat(converter.canRead(MyBean.class, halJsonMediaType)).isTrue();
@ -121,9 +121,9 @@ class JacksonJsonHttpMessageConverterTests {
assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(defaultMediaTypes); assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(defaultMediaTypes);
MediaType halJson = MediaType.parseMediaType("application/hal+json"); MediaType halJson = MediaType.parseMediaType("application/hal+json");
converter.registerObjectMappersForType(MyBean.class, map -> { converter.registerMappersForType(MyBean.class, map -> {
map.put(halJson, new ObjectMapper()); map.put(halJson, new JsonMapper());
map.put(MediaType.APPLICATION_JSON, new ObjectMapper()); map.put(MediaType.APPLICATION_JSON, new JsonMapper());
}); });
assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(halJson, MediaType.APPLICATION_JSON); assertThat(converter.getSupportedMediaTypes(MyBean.class)).containsExactly(halJson, MediaType.APPLICATION_JSON);
@ -365,7 +365,7 @@ class JacksonJsonHttpMessageConverterTests {
PrettyPrintBean bean = new PrettyPrintBean(); PrettyPrintBean bean = new PrettyPrintBean();
bean.setName("Jason"); bean.setName("Jason");
ObjectMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build(); JsonMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
this.converter = new JacksonJsonHttpMessageConverter(mapper); this.converter = new JacksonJsonHttpMessageConverter(mapper);
this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class), this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class),
MediaType.APPLICATION_JSON, outputMessage, null); MediaType.APPLICATION_JSON, outputMessage, null);
@ -384,7 +384,7 @@ class JacksonJsonHttpMessageConverterTests {
PrettyPrintBean bean = new PrettyPrintBean(); PrettyPrintBean bean = new PrettyPrintBean();
bean.setName("Jason"); bean.setName("Jason");
ObjectMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build(); JsonMapper mapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
this.converter = new JacksonJsonHttpMessageConverter(mapper); this.converter = new JacksonJsonHttpMessageConverter(mapper);
this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class), this.converter.write(bean, ResolvableType.forType(PrettyPrintBean.class),
MediaType.APPLICATION_JSON, outputMessage, null); MediaType.APPLICATION_JSON, outputMessage, null);

5
spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java

@ -33,7 +33,6 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -439,10 +438,10 @@ class ResponseEntityResultHandlerTests {
MediaType halFormsMediaType = MediaType.parseMediaType("application/prs.hal-forms+json"); MediaType halFormsMediaType = MediaType.parseMediaType("application/prs.hal-forms+json");
MediaType halMediaType = MediaType.parseMediaType("application/hal+json"); MediaType halMediaType = MediaType.parseMediaType("application/hal+json");
ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build(); JsonMapper jsonMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
JacksonJsonEncoder encoder = new JacksonJsonEncoder(); JacksonJsonEncoder encoder = new JacksonJsonEncoder();
encoder.registerObjectMappersForType(Person.class, map -> map.put(halMediaType, objectMapper)); encoder.registerMappersForType(Person.class, map -> map.put(halMediaType, jsonMapper));
EncoderHttpMessageWriter<?> writer = new EncoderHttpMessageWriter<>(encoder); EncoderHttpMessageWriter<?> writer = new EncoderHttpMessageWriter<>(encoder);
ResponseEntityResultHandler handler = new ResponseEntityResultHandler( ResponseEntityResultHandler handler = new ResponseEntityResultHandler(

9
spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/JacksonJsonView.java

@ -24,7 +24,6 @@ import java.util.Set;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -35,7 +34,7 @@ import org.springframework.web.servlet.view.AbstractJacksonView;
/** /**
* Spring MVC {@link View} that renders JSON content by serializing the model for the current request * Spring MVC {@link View} that renders JSON content by serializing the model for the current request
* using <a href="https://github.com/FasterXML/jackson">Jackson 3's</a> {@link ObjectMapper}. * using <a href="https://github.com/FasterXML/jackson">Jackson 3's</a> {@link JsonMapper}.
* *
* <p>By default, the entire contents of the model map (with the exception of framework-specific classes) * <p>By default, the entire contents of the model map (with the exception of framework-specific classes)
* will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON * will be encoded as JSON. If the model contains only one key, you can have it extracted encoded as JSON
@ -79,11 +78,11 @@ public class JacksonJsonView extends AbstractJacksonView {
} }
/** /**
* Construct a new instance using the provided {@link ObjectMapper} * Construct a new instance using the provided {@link JsonMapper}
* and setting the content type to {@value #DEFAULT_CONTENT_TYPE}. * and setting the content type to {@value #DEFAULT_CONTENT_TYPE}.
*/ */
public JacksonJsonView(ObjectMapper objectMapper) { public JacksonJsonView(JsonMapper jsonMapper) {
super(objectMapper, DEFAULT_CONTENT_TYPE); super(jsonMapper, DEFAULT_CONTENT_TYPE);
} }

10
spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java

@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.DeserializationFeature; import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.MapperFeature; import tools.jackson.databind.MapperFeature;
import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper;
import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.TestBean;
@ -214,10 +214,10 @@ class WebMvcConfigurationSupportExtensionTests {
assertThat(converters.get(0).getClass()).isEqualTo(StringHttpMessageConverter.class); assertThat(converters.get(0).getClass()).isEqualTo(StringHttpMessageConverter.class);
assertThat(converters.get(1).getClass()).isEqualTo(AllEncompassingFormHttpMessageConverter.class); assertThat(converters.get(1).getClass()).isEqualTo(AllEncompassingFormHttpMessageConverter.class);
assertThat(converters.get(2).getClass()).isEqualTo(JacksonJsonHttpMessageConverter.class); assertThat(converters.get(2).getClass()).isEqualTo(JacksonJsonHttpMessageConverter.class);
ObjectMapper objectMapper = ((JacksonJsonHttpMessageConverter) converters.get(2)).getObjectMapper(); JsonMapper jsonMapper = ((JacksonJsonHttpMessageConverter) converters.get(2)).getMapper();
assertThat(objectMapper.deserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); assertThat(jsonMapper.deserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse();
assertThat(objectMapper.deserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); assertThat(jsonMapper.deserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
assertThat(objectMapper.serializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); assertThat(jsonMapper.serializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse();
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter); DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(adapter);

2
spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java

@ -181,7 +181,7 @@ class WebMvcConfigurationSupportTests {
.filter(AbstractJacksonHttpMessageConverter.class::isInstance) .filter(AbstractJacksonHttpMessageConverter.class::isInstance)
.map(AbstractJacksonHttpMessageConverter.class::cast) .map(AbstractJacksonHttpMessageConverter.class::cast)
.forEach(converter -> { .forEach(converter -> {
ObjectMapper mapper = converter.getObjectMapper(); ObjectMapper mapper = converter.getMapper();
assertThat(mapper.deserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); assertThat(mapper.deserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse();
assertThat(mapper.deserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); assertThat(mapper.deserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse();
assertThat(mapper.serializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); assertThat(mapper.serializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse();

5
spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java

@ -24,7 +24,6 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -109,8 +108,8 @@ class SseServerResponseTests {
} }
}); });
ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build(); JsonMapper jsonMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter(objectMapper); JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter(jsonMapper);
ServerResponse.Context context = () -> List.of(converter); ServerResponse.Context context = () -> List.of(converter);
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context); ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);

2
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java

@ -338,7 +338,7 @@ class RequestResponseBodyMethodProcessorTests {
simpleBean.setName("Jason"); simpleBean.setName("Jason");
JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter(); JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter();
converter.registerObjectMappersForType(SimpleBean.class, map -> map.put(halMediaType, mapper)); converter.registerMappersForType(SimpleBean.class, map -> map.put(halMediaType, mapper));
RequestResponseBodyMethodProcessor processor = RequestResponseBodyMethodProcessor processor =
new RequestResponseBodyMethodProcessor(List.of(converter)); new RequestResponseBodyMethodProcessor(List.of(converter));
MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("getSimpleBean"), -1); MethodParameter returnType = new MethodParameter(getClass().getDeclaredMethod("getSimpleBean"), -1);

3
spring-webmvc/src/test/java/org/springframework/web/servlet/view/json/JacksonJsonViewTests.java

@ -33,7 +33,6 @@ import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator; import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.BeanDescription; import tools.jackson.databind.BeanDescription;
import tools.jackson.databind.JavaType; import tools.jackson.databind.JavaType;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationContext; import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.ValueSerializer; import tools.jackson.databind.ValueSerializer;
@ -181,7 +180,7 @@ class JacksonJsonViewTests {
@Test @Test
void renderWithCustomSerializerLocatedByFactory() throws Exception { void renderWithCustomSerializerLocatedByFactory() throws Exception {
SerializerFactory factory = new DelegatingSerializerFactory(null); SerializerFactory factory = new DelegatingSerializerFactory(null);
ObjectMapper mapper = JsonMapper.builder().serializerFactory(factory).build(); JsonMapper mapper = JsonMapper.builder().serializerFactory(factory).build();
view = new JacksonJsonView(mapper); view = new JacksonJsonView(mapper);
Object bean = new TestBeanSimple(); Object bean = new TestBeanSimple();

17
spring-websocket/src/main/java/org/springframework/web/socket/sockjs/frame/JacksonJsonSockJsMessageCodec.java

@ -20,7 +20,6 @@ import java.io.InputStream;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import tools.jackson.core.io.JsonStringEncoder; import tools.jackson.core.io.JsonStringEncoder;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.cfg.MapperBuilder; import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.json.JsonMapper; import tools.jackson.databind.json.JsonMapper;
@ -37,7 +36,7 @@ import org.springframework.util.Assert;
*/ */
public class JacksonJsonSockJsMessageCodec extends AbstractSockJsMessageCodec { public class JacksonJsonSockJsMessageCodec extends AbstractSockJsMessageCodec {
private final ObjectMapper objectMapper; private final JsonMapper jsonMapper;
/** /**
@ -46,28 +45,28 @@ public class JacksonJsonSockJsMessageCodec extends AbstractSockJsMessageCodec {
* {@link MapperBuilder#findModules(ClassLoader)}. * {@link MapperBuilder#findModules(ClassLoader)}.
*/ */
public JacksonJsonSockJsMessageCodec() { public JacksonJsonSockJsMessageCodec() {
this.objectMapper = JsonMapper.builder().findAndAddModules(JacksonJsonSockJsMessageCodec.class.getClassLoader()).build(); this.jsonMapper = JsonMapper.builder().findAndAddModules(JacksonJsonSockJsMessageCodec.class.getClassLoader()).build();
} }
/** /**
* Construct a new instance with the provided {@link ObjectMapper}. * Construct a new instance with the provided {@link JsonMapper}.
* @see JsonMapper#builder() * @see JsonMapper#builder()
* @see MapperBuilder#findAndAddModules(ClassLoader) * @see MapperBuilder#findAndAddModules(ClassLoader)
*/ */
public JacksonJsonSockJsMessageCodec(ObjectMapper objectMapper) { public JacksonJsonSockJsMessageCodec(JsonMapper jsonMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null"); Assert.notNull(jsonMapper, "JsonMapper must not be null");
this.objectMapper = objectMapper; this.jsonMapper = jsonMapper;
} }
@Override @Override
public String @Nullable [] decode(String content) { public String @Nullable [] decode(String content) {
return this.objectMapper.readValue(content, String[].class); return this.jsonMapper.readValue(content, String[].class);
} }
@Override @Override
public String @Nullable [] decodeInputStream(InputStream content) { public String @Nullable [] decodeInputStream(InputStream content) {
return this.objectMapper.readValue(content, String[].class); return this.jsonMapper.readValue(content, String[].class);
} }
@Override @Override

Loading…
Cancel
Save