Skip to content

Commit 01ce468

Browse files
committed
SPR-6386 - MappingJacksonHttpMessageConverter ignores supported media types property
1 parent dc0613f commit 01ce468

File tree

2 files changed

+68
-29
lines changed

2 files changed

+68
-29
lines changed

org.springframework.web/src/main/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverter.java

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020
import java.nio.charset.Charset;
2121
import java.util.List;
2222

23+
import javax.xml.bind.Marshaller;
24+
import javax.xml.bind.PropertyException;
25+
2326
import org.codehaus.jackson.JsonEncoding;
2427
import org.codehaus.jackson.JsonGenerator;
28+
import org.codehaus.jackson.type.JavaType;
2529
import org.codehaus.jackson.map.ObjectMapper;
30+
import org.codehaus.jackson.map.type.TypeFactory;
2631

2732
import org.springframework.http.HttpInputMessage;
2833
import org.springframework.http.HttpOutputMessage;
@@ -39,24 +44,24 @@
3944
* <p>This converter can be used to bind to typed beans, or untyped {@link java.util.HashMap HashMap} instances.
4045
*
4146
* <p>By default, this converter supports {@code application/json}. This can be overridden by setting the {@link
42-
* #setSupportedMediaTypes(List) supportedMediaTypes} property, and overriding the {@link #getContentType(Object)}
47+
* #setSupportedMediaTypes(List) supportedMediaTypes} property.
4348
* method.
4449
*
4550
* @author Arjen Poutsma
4651
* @see org.springframework.web.servlet.view.json.BindingJacksonJsonView
4752
* @since 3.0
4853
*/
49-
public class MappingJacksonHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> {
54+
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
5055

51-
private ObjectMapper objectMapper = new ObjectMapper();
56+
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
5257

53-
private JsonEncoding encoding = JsonEncoding.UTF8;
58+
private ObjectMapper objectMapper = new ObjectMapper();
5459

5560
private boolean prefixJson = false;
5661

5762
/** Construct a new {@code BindingJacksonHttpMessageConverter}, */
5863
public MappingJacksonHttpMessageConverter() {
59-
super(new MediaType("application", "json"));
64+
super(new MediaType("application", "json", DEFAULT_CHARSET));
6065
}
6166

6267
/**
@@ -73,12 +78,6 @@ public void setObjectMapper(ObjectMapper objectMapper) {
7378
this.objectMapper = objectMapper;
7479
}
7580

76-
/** Sets the {@code JsonEncoding} for this converter. By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */
77-
public void setEncoding(JsonEncoding encoding) {
78-
Assert.notNull(encoding, "'encoding' must not be null");
79-
this.encoding = encoding;
80-
}
81-
8281
/**
8382
* Indicates whether the JSON output by this view should be prefixed with "{} &&". Default is false.
8483
*
@@ -91,30 +90,50 @@ public void setPrefixJson(boolean prefixJson) {
9190
}
9291

9392
@Override
94-
public boolean supports(Class<? extends T> clazz) {
95-
return objectMapper.canSerialize(clazz);
93+
public boolean canRead(Class<?> clazz, MediaType mediaType) {
94+
JavaType javaType = TypeFactory.fromClass(clazz);
95+
return objectMapper.canDeserialize(javaType) && isSupported(mediaType);
9696
}
9797

9898
@Override
99-
protected T readInternal(Class<T> clazz, HttpInputMessage inputMessage)
100-
throws IOException, HttpMessageNotReadableException {
101-
return objectMapper.readValue(inputMessage.getBody(), clazz);
99+
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
100+
return objectMapper.canSerialize(clazz) && isSupported(mediaType);
101+
}
102+
103+
@Override
104+
protected boolean supports(Class<?> clazz) {
105+
// should not be called, since we override canRead/Write
106+
throw new UnsupportedOperationException();
102107
}
103108

104109
@Override
105-
protected MediaType getDefaultContentType(T t) {
106-
Charset charset = Charset.forName(encoding.getJavaName());
107-
return new MediaType("application", "json", charset);
110+
protected Object readInternal(Class<Object> clazz, HttpInputMessage inputMessage)
111+
throws IOException, HttpMessageNotReadableException {
112+
return objectMapper.readValue(inputMessage.getBody(), clazz);
108113
}
109114

110115
@Override
111-
protected void writeInternal(T t, HttpOutputMessage outputMessage)
116+
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
112117
throws IOException, HttpMessageNotWritableException {
118+
JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType());
113119
JsonGenerator jsonGenerator =
114120
objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
115121
if (prefixJson) {
116122
jsonGenerator.writeRaw("{} && ");
117123
}
118-
objectMapper.writeValue(jsonGenerator, t);
124+
objectMapper.writeValue(jsonGenerator, o);
119125
}
126+
127+
private JsonEncoding getEncoding(MediaType contentType) {
128+
if (contentType != null && contentType.getCharSet() != null) {
129+
Charset charset = contentType.getCharSet();
130+
for (JsonEncoding encoding : JsonEncoding.values()) {
131+
if (charset.name().equals(encoding.getJavaName())) {
132+
return encoding;
133+
}
134+
}
135+
}
136+
return JsonEncoding.UTF8;
137+
}
138+
120139
}

org.springframework.web/src/test/java/org/springframework/http/converter/json/MappingJacksonHttpMessageConverterTests.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.HashMap;
2323
import java.util.List;
24+
import java.util.Map;
2425

2526
import static org.junit.Assert.*;
2627
import org.junit.Before;
@@ -33,25 +34,33 @@
3334
/** @author Arjen Poutsma */
3435
public class MappingJacksonHttpMessageConverterTests {
3536

36-
private MappingJacksonHttpMessageConverter<MyBean> converter;
37+
private MappingJacksonHttpMessageConverter converter;
3738

3839
@Before
3940
public void setUp() {
40-
converter = new MappingJacksonHttpMessageConverter<MyBean>();
41+
converter = new MappingJacksonHttpMessageConverter();
4142
}
4243

4344
@Test
44-
public void supports() {
45-
assertTrue(converter.supports(MyBean.class));
45+
public void canRead() {
46+
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json")));
47+
assertTrue(converter.canRead(Map.class, new MediaType("application", "json")));
4648
}
4749

4850
@Test
51+
public void canWrite() {
52+
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json")));
53+
assertTrue(converter.canWrite(Map.class, new MediaType("application", "json")));
54+
}
55+
56+
@Test
57+
@SuppressWarnings("unchecked")
4958
public void readTyped() throws IOException {
5059
String body =
5160
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
5261
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
5362
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
54-
MyBean result = converter.read(MyBean.class, inputMessage);
63+
MyBean result = (MyBean) converter.read((Class) MyBean.class, inputMessage);
5564
assertEquals("Foo", result.getString());
5665
assertEquals(42, result.getNumber());
5766
assertEquals(42F, result.getFraction(), 0F);
@@ -61,14 +70,13 @@ public void readTyped() throws IOException {
6170
}
6271

6372
@Test
64-
@SuppressWarnings({"unchecked"})
73+
@SuppressWarnings("unchecked")
6574
public void readUntyped() throws IOException {
66-
MappingJacksonHttpMessageConverter<HashMap> converter = new MappingJacksonHttpMessageConverter<HashMap>();
6775
String body =
6876
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
6977
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
7078
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
71-
HashMap<String, Object> result = converter.read(HashMap.class, inputMessage);
79+
HashMap<String, Object> result = (HashMap<String, Object>) converter.read((Class)HashMap.class, inputMessage);
7280
assertEquals("Foo", result.get("string"));
7381
assertEquals(42, result.get("number"));
7482
assertEquals(42D, (Double) result.get("fraction"), 0D);
@@ -103,6 +111,18 @@ public void write() throws IOException {
103111
outputMessage.getHeaders().getContentType());
104112
}
105113

114+
@Test
115+
public void writeUTF16() throws IOException {
116+
Charset utf16 = Charset.forName("UTF-16BE");
117+
MediaType contentType = new MediaType("application", "json", utf16);
118+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
119+
String body = "H\u00e9llo W\u00f6rld";
120+
converter.write(body, contentType, outputMessage);
121+
assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16));
122+
assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
123+
}
124+
125+
106126
public static class MyBean {
107127

108128
private String string;

0 commit comments

Comments
 (0)