Use Jackson improved default configuration everywhere
With this commit, Jackson builder is now used in spring-websocket to create the ObjectMapper instance. It is not possible to use the builder for spring-messaging and spring-jms since these modules don't have a dependency on spring-web, thus they now just customize the same features: - MapperFeature#DEFAULT_VIEW_INCLUSION is disabled - DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES is disabled Issue: SPR-12293
This commit is contained in:
parent
87f1512e88
commit
fbd85925de
|
@ -29,7 +29,9 @@ import javax.jms.Message;
|
|||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
|
@ -42,6 +44,12 @@ import org.springframework.util.ClassUtils;
|
|||
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
|
||||
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
|
||||
*
|
||||
* <p>It customizes Jackson's default properties with the following ones:
|
||||
* <ul>
|
||||
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
|
||||
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Tested against Jackson 2.2; compatible with Jackson 2.0 and higher.
|
||||
*
|
||||
* @author Mark Pollack
|
||||
|
@ -57,7 +65,7 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
|
|||
public static final String DEFAULT_ENCODING = "UTF-8";
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private MessageType targetType = MessageType.BYTES;
|
||||
|
||||
|
@ -74,6 +82,12 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
|
|||
private ClassLoader beanClassLoader;
|
||||
|
||||
|
||||
public MappingJackson2MessageConverter() {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@link ObjectMapper} to use instead of using the default.
|
||||
*/
|
||||
|
|
|
@ -115,6 +115,32 @@ public class MappingJackson2MessageConverterTests {
|
|||
verify(textMessageMock).setStringProperty("__typeid__", HashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromTextMessage() throws Exception {
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
MyBean unmarshalled = new MyBean("bar");
|
||||
|
||||
String text = "{\"foo\":\"bar\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
MyBean result = (MyBean)converter.fromMessage(textMessageMock);
|
||||
assertEquals("Invalid result", result, unmarshalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromTextMessageWithUnknownProperty() throws Exception {
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
MyBean unmarshalled = new MyBean("bar");
|
||||
|
||||
String text = "{\"foo\":\"bar\", \"unknownProperty\":\"value\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
MyBean result = (MyBean)converter.fromMessage(textMessageMock);
|
||||
assertEquals("Invalid result", result, unmarshalled);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromTextMessageAsObject() throws Exception {
|
||||
TextMessage textMessageMock = mock(TextMessage.class);
|
||||
|
@ -141,4 +167,47 @@ public class MappingJackson2MessageConverterTests {
|
|||
assertEquals("Invalid result", result, unmarshalled);
|
||||
}
|
||||
|
||||
public static class MyBean {
|
||||
|
||||
public MyBean() {
|
||||
}
|
||||
|
||||
public MyBean(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
private String foo;
|
||||
|
||||
public String getFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MyBean bean = (MyBean) o;
|
||||
|
||||
if (foo != null ? !foo.equals(bean.foo) : bean.foo != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return foo != null ? foo.hashCode() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,7 +26,9 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
|
@ -39,6 +41,12 @@ import org.springframework.util.MimeType;
|
|||
/**
|
||||
* A Jackson 2 based {@link MessageConverter} implementation.
|
||||
*
|
||||
* <p>It customizes Jackson's default properties with the following ones:
|
||||
* <ul>
|
||||
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
|
||||
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Compatible with Jackson 2.1 and higher.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
|
@ -52,16 +60,18 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
|||
ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
|
||||
|
||||
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private Boolean prettyPrint;
|
||||
|
||||
|
||||
public MappingJackson2MessageConverter() {
|
||||
super(new MimeType("application", "json", Charset.forName("UTF-8")));
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this converter.
|
||||
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
|
||||
|
|
|
@ -289,16 +289,20 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
|||
converters.add(new StringMessageConverter());
|
||||
converters.add(new ByteArrayMessageConverter());
|
||||
if (jackson2Present) {
|
||||
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
|
||||
resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
converter.setContentTypeResolver(resolver);
|
||||
converters.add(converter);
|
||||
converters.add(createJacksonConverter());
|
||||
}
|
||||
}
|
||||
return new CompositeMessageConverter(converters);
|
||||
}
|
||||
|
||||
protected MappingJackson2MessageConverter createJacksonConverter() {
|
||||
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
|
||||
resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
|
||||
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
|
||||
converter.setContentTypeResolver(resolver);
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to add custom message converters.
|
||||
* @param messageConverters the list to add converters to, initially empty
|
||||
|
|
|
@ -87,11 +87,12 @@ public class MappingJackson2MessageConverterTests {
|
|||
this.converter.fromMessage(message, MyBean.class);
|
||||
}
|
||||
|
||||
@Test(expected = MessageConversionException.class)
|
||||
@Test
|
||||
public void fromMessageValidJsonWithUnknownProperty() throws IOException {
|
||||
String payload = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
|
||||
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
|
||||
this.converter.fromMessage(message, MyBean.class);
|
||||
MyBean myBean = (MyBean)this.converter.fromMessage(message, MyBean.class);
|
||||
assertEquals("string", myBean.getString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -434,12 +434,7 @@ public class Jackson2ObjectMapperBuilder {
|
|||
objectMapper.registerModule(module);
|
||||
}
|
||||
|
||||
if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
|
||||
configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
|
||||
}
|
||||
if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
|
||||
configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
customizeDefaultFeatures(objectMapper);
|
||||
for (Object feature : this.features.keySet()) {
|
||||
configureFeature(objectMapper, feature, this.features.get(feature));
|
||||
}
|
||||
|
@ -475,6 +470,17 @@ public class Jackson2ObjectMapperBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
// Any change to this method should be also applied to spring-jms and spring-messaging
|
||||
// MappingJackson2MessageConverter default constructors
|
||||
private void customizeDefaultFeatures(ObjectMapper objectMapper) {
|
||||
if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
|
||||
configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
|
||||
}
|
||||
if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
|
||||
configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> void addSerializers(SimpleModule module) {
|
||||
for (Class<?> type : this.serializers.keySet()) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
|
@ -385,6 +386,8 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
|
|||
RootBeanDefinition resolverDef = new RootBeanDefinition(DefaultContentTypeResolver.class);
|
||||
resolverDef.getPropertyValues().add("defaultMimeType", MimeTypeUtils.APPLICATION_JSON);
|
||||
jacksonConverterDef.getPropertyValues().add("contentTypeResolver", resolverDef);
|
||||
// Use Jackson builder in order to have JSR-310 and Joda-Time modules registered automatically
|
||||
jacksonConverterDef.getPropertyValues().add("objectMapper", Jackson2ObjectMapperBuilder.json().build());
|
||||
converters.add(jacksonConverterDef);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.web.socket.config.annotation;
|
|||
|
||||
import org.springframework.beans.factory.config.CustomScopeConfigurer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.simp.SimpSessionScope;
|
||||
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
|
||||
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
|
||||
|
@ -132,4 +134,12 @@ public abstract class WebSocketMessageBrokerConfigurationSupport extends Abstrac
|
|||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MappingJackson2MessageConverter createJacksonConverter() {
|
||||
MappingJackson2MessageConverter messageConverter = super.createJacksonConverter();
|
||||
// Use Jackson builder in order to have JSR-310 and Joda-Time modules registered automatically
|
||||
messageConverter.setObjectMapper(Jackson2ObjectMapperBuilder.json().build());
|
||||
return messageConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,13 +20,25 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
|
||||
import com.fasterxml.jackson.core.io.JsonStringEncoder;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A Jackson 2.x codec for encoding and decoding SockJS messages.
|
||||
*
|
||||
* <p>It customizes Jackson's default properties with the following ones:
|
||||
* <ul>
|
||||
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
|
||||
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically
|
||||
* when available (and when Java 8 and Joda-Time themselves are available, respectively).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
|
@ -36,7 +48,7 @@ public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
|
|||
|
||||
|
||||
public Jackson2SockJsMessageCodec() {
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.objectMapper = Jackson2ObjectMapperBuilder.json().build();
|
||||
}
|
||||
|
||||
public Jackson2SockJsMessageCodec(ObjectMapper objectMapper) {
|
||||
|
|
Loading…
Reference in New Issue