Skip to content

Commit

Permalink
SPR-6386 - MappingJacksonHttpMessageConverter ignores supported media…
Browse files Browse the repository at this point in the history
… types property
  • Loading branch information
poutsma committed Nov 27, 2009
1 parent dc0613f commit 01ce468
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@
import java.nio.charset.Charset;
import java.util.List;

import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;

import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;

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

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

private JsonEncoding encoding = JsonEncoding.UTF8;
private ObjectMapper objectMapper = new ObjectMapper();

private boolean prefixJson = false;

/** Construct a new {@code BindingJacksonHttpMessageConverter}, */
public MappingJacksonHttpMessageConverter() {
super(new MediaType("application", "json"));
super(new MediaType("application", "json", DEFAULT_CHARSET));
}

/**
Expand All @@ -73,12 +78,6 @@ public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

/** Sets the {@code JsonEncoding} for this converter. By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */
public void setEncoding(JsonEncoding encoding) {
Assert.notNull(encoding, "'encoding' must not be null");
this.encoding = encoding;
}

/**
* Indicates whether the JSON output by this view should be prefixed with "{} &&". Default is false.
*
Expand All @@ -91,30 +90,50 @@ public void setPrefixJson(boolean prefixJson) {
}

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

@Override
protected T readInternal(Class<T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return objectMapper.readValue(inputMessage.getBody(), clazz);
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return objectMapper.canSerialize(clazz) && isSupported(mediaType);
}

@Override
protected boolean supports(Class<?> clazz) {
// should not be called, since we override canRead/Write
throw new UnsupportedOperationException();
}

@Override
protected MediaType getDefaultContentType(T t) {
Charset charset = Charset.forName(encoding.getJavaName());
return new MediaType("application", "json", charset);
protected Object readInternal(Class<Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return objectMapper.readValue(inputMessage.getBody(), clazz);
}

@Override
protected void writeInternal(T t, HttpOutputMessage outputMessage)
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
if (prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
objectMapper.writeValue(jsonGenerator, t);
objectMapper.writeValue(jsonGenerator, o);
}

private JsonEncoding getEncoding(MediaType contentType) {
if (contentType != null && contentType.getCharSet() != null) {
Charset charset = contentType.getCharSet();
for (JsonEncoding encoding : JsonEncoding.values()) {
if (charset.name().equals(encoding.getJavaName())) {
return encoding;
}
}
}
return JsonEncoding.UTF8;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;
import org.junit.Before;
Expand All @@ -33,25 +34,33 @@
/** @author Arjen Poutsma */
public class MappingJacksonHttpMessageConverterTests {

private MappingJacksonHttpMessageConverter<MyBean> converter;
private MappingJacksonHttpMessageConverter converter;

@Before
public void setUp() {
converter = new MappingJacksonHttpMessageConverter<MyBean>();
converter = new MappingJacksonHttpMessageConverter();
}

@Test
public void supports() {
assertTrue(converter.supports(MyBean.class));
public void canRead() {
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json")));
assertTrue(converter.canRead(Map.class, new MediaType("application", "json")));
}

@Test
public void canWrite() {
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json")));
assertTrue(converter.canWrite(Map.class, new MediaType("application", "json")));
}

@Test
@SuppressWarnings("unchecked")
public void readTyped() throws IOException {
String body =
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
MyBean result = converter.read(MyBean.class, inputMessage);
MyBean result = (MyBean) converter.read((Class) MyBean.class, inputMessage);
assertEquals("Foo", result.getString());
assertEquals(42, result.getNumber());
assertEquals(42F, result.getFraction(), 0F);
Expand All @@ -61,14 +70,13 @@ public void readTyped() throws IOException {
}

@Test
@SuppressWarnings({"unchecked"})
@SuppressWarnings("unchecked")
public void readUntyped() throws IOException {
MappingJacksonHttpMessageConverter<HashMap> converter = new MappingJacksonHttpMessageConverter<HashMap>();
String body =
"{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
HashMap<String, Object> result = converter.read(HashMap.class, inputMessage);
HashMap<String, Object> result = (HashMap<String, Object>) converter.read((Class)HashMap.class, inputMessage);
assertEquals("Foo", result.get("string"));
assertEquals(42, result.get("number"));
assertEquals(42D, (Double) result.get("fraction"), 0D);
Expand Down Expand Up @@ -103,6 +111,18 @@ public void write() throws IOException {
outputMessage.getHeaders().getContentType());
}

@Test
public void writeUTF16() throws IOException {
Charset utf16 = Charset.forName("UTF-16BE");
MediaType contentType = new MediaType("application", "json", utf16);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
String body = "H\u00e9llo W\u00f6rld";
converter.write(body, contentType, outputMessage);
assertEquals("Invalid result", "\"" + body + "\"", outputMessage.getBodyAsString(utf16));
assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
}


public static class MyBean {

private String string;
Expand Down

0 comments on commit 01ce468

Please sign in to comment.