Add support for Jackson serialization views

Spring MVC now supports Jackon's serialization views for rendering
different subsets of the same POJO from different controller
methods (e.g. detailed page vs summary view).

Issue: SPR-7156
This commit is contained in:
Sebastien Deleuze
2014-05-12 17:05:35 -04:00
committed by Rossen Stoyanchev
parent 673a497923
commit be0b69cbf1
15 changed files with 610 additions and 16 deletions

View File

@@ -90,6 +90,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
public void testBooleanSetters() {
this.factory.setAutoDetectFields(false);
this.factory.setAutoDetectGettersSetters(false);
this.factory.setDefaultViewInclusion(false);
this.factory.setFailOnEmptyBeans(false);
this.factory.setIndentOutput(true);
this.factory.afterPropertiesSet();
@@ -100,6 +101,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS));
assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.AUTO_DETECT_GETTERS));
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_SETTERS));
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
assertFalse(objectMapper.getSerializationConfig().isEnabled(SerializationFeature.FAIL_ON_EMPTY_BEANS));
assertTrue(objectMapper.getSerializationConfig().isEnabled(SerializationFeature.INDENT_OUTPUT));
assertTrue(objectMapper.getSerializationConfig().getSerializationInclusion() == JsonInclude.Include.ALWAYS);
@@ -253,6 +255,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertTrue(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS));
assertFalse(objectMapper.getSerializationConfig().isEnabled(MapperFeature.AUTO_DETECT_GETTERS));
assertTrue(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION));
assertFalse(objectMapper.getDeserializationConfig().isEnabled(MapperFeature.AUTO_DETECT_FIELDS));
assertFalse(objectMapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE));
assertFalse(objectMapper.getFactory().isEnabled(JsonGenerator.Feature.QUOTE_FIELD_NAMES));

View File

@@ -24,6 +24,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
@@ -237,6 +238,22 @@ public class MappingJackson2HttpMessageConverterTests {
assertEquals(")]}',\"foo\"", outputMessage.getBodyAsString(Charset.forName("UTF-8")));
}
@Test
public void jsonView() throws Exception {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
JacksonViewBean bean = new JacksonViewBean();
bean.setWithView1("with");
bean.setWithView2("with");
bean.setWithoutView("without");
MappingJacksonValueHolder jsv = new MappingJacksonValueHolder(bean, MyJacksonView1.class);
this.converter.writeInternal(jsv, outputMessage);
String result = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
assertTrue(result.contains("\"withView1\":\"with\""));
assertFalse(result.contains("\"withView2\":\"with\""));
assertTrue(result.contains("\"withoutView\":\"without\""));
}
public static class MyBean {
@@ -315,4 +332,42 @@ public class MappingJackson2HttpMessageConverterTests {
}
}
private interface MyJacksonView1 {};
private interface MyJacksonView2 {};
private static class JacksonViewBean {
@JsonView(MyJacksonView1.class)
private String withView1;
@JsonView(MyJacksonView2.class)
private String withView2;
private String withoutView;
public String getWithView1() {
return withView1;
}
public void setWithView1(String withView1) {
this.withView1 = withView1;
}
public String getWithView2() {
return withView2;
}
public void setWithView2(String withView2) {
this.withView2 = withView2;
}
public String getWithoutView() {
return withoutView;
}
public void setWithoutView(String withoutView) {
this.withoutView = withoutView;
}
}
}

View File

@@ -56,7 +56,8 @@ public class AbstractJettyServerTestCase {
protected static String baseUrl;
protected static MediaType contentType;
protected static MediaType textContentType;
protected static MediaType jsonContentType;
private static Server jettyServer;
@@ -67,14 +68,19 @@ public class AbstractJettyServerTestCase {
baseUrl = "http://localhost:" + port;
ServletContextHandler handler = new ServletContextHandler();
byte[] bytes = helloWorld.getBytes("UTF-8");
contentType = new MediaType("text", "plain", Collections
textContentType = new MediaType("text", "plain", Collections
.singletonMap("charset", "UTF-8"));
handler.addServlet(new ServletHolder(new GetServlet(bytes, contentType)), "/get");
handler.addServlet(new ServletHolder(new GetServlet(new byte[0], contentType)), "/get/nothing");
jsonContentType = new MediaType("application", "json", Collections
.singletonMap("charset", "UTF-8"));
handler.addServlet(new ServletHolder(new GetServlet(bytes, textContentType)), "/get");
handler.addServlet(new ServletHolder(new GetServlet(new byte[0], textContentType)), "/get/nothing");
handler.addServlet(new ServletHolder(new GetServlet(bytes, null)), "/get/nocontenttype");
handler.addServlet(
new ServletHolder(new PostServlet(helloWorld, baseUrl + "/post/1", bytes, contentType)),
new ServletHolder(new PostServlet(helloWorld, baseUrl + "/post/1", bytes, textContentType)),
"/post");
handler.addServlet(
new ServletHolder(new JsonPostServlet(baseUrl + "/jsonpost/1", jsonContentType)),
"/jsonpost");
handler.addServlet(new ServletHolder(new StatusCodeServlet(204)), "/status/nocontent");
handler.addServlet(new ServletHolder(new StatusCodeServlet(304)), "/status/notmodified");
handler.addServlet(new ServletHolder(new ErrorServlet(404)), "/status/notfound");
@@ -83,7 +89,7 @@ public class AbstractJettyServerTestCase {
handler.addServlet(new ServletHolder(new MultipartServlet()), "/multipart");
handler.addServlet(new ServletHolder(new DeleteServlet()), "/delete");
handler.addServlet(
new ServletHolder(new PutServlet(helloWorld, bytes, contentType)),
new ServletHolder(new PutServlet(helloWorld, bytes, textContentType)),
"/put");
jettyServer.setHandler(handler);
jettyServer.start();
@@ -185,6 +191,33 @@ public class AbstractJettyServerTestCase {
}
}
@SuppressWarnings("serial")
private static class JsonPostServlet extends HttpServlet {
private final String location;
private final MediaType contentType;
private JsonPostServlet(String location, MediaType contentType) {
this.location = location;
this.contentType = contentType;
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
assertTrue("Invalid request content-length", request.getContentLength() > 0);
assertNotNull("No content-type", request.getContentType());
String body = FileCopyUtils.copyToString(request.getReader());
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("Location", location);
response.setContentType(contentType.toString());
byte[] bytes = body.getBytes("UTF-8");
response.setContentLength(bytes.length);;
FileCopyUtils.copy(bytes, response.getOutputStream());
}
}
@SuppressWarnings("serial")
private static class PutServlet extends HttpServlet {

View File

@@ -63,7 +63,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
ResponseEntity<String> entity = futureEntity.get();
assertEquals("Invalid content", helloWorld, entity.getBody());
assertFalse("No headers", entity.getHeaders().isEmpty());
assertEquals("Invalid content-type", contentType, entity.getHeaders().getContentType());
assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
}
@@ -84,7 +84,7 @@ public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCa
public void onSuccess(ResponseEntity<String> entity) {
assertEquals("Invalid content", helloWorld, entity.getBody());
assertFalse("No headers", entity.getHeaders().isEmpty());
assertEquals("Invalid content-type", contentType, entity.getHeaders().getContentType());
assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
}

View File

@@ -23,6 +23,7 @@ import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonView;
import org.junit.Before;
import org.junit.Test;
@@ -35,6 +36,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJacksonValueHolder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -63,7 +65,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
ResponseEntity<String> entity = template.getForEntity(baseUrl + "/{method}", String.class, "get");
assertEquals("Invalid content", helloWorld, entity.getBody());
assertFalse("No headers", entity.getHeaders().isEmpty());
assertEquals("Invalid content-type", contentType, entity.getHeaders().getContentType());
assertEquals("Invalid content-type", textContentType, entity.getHeaders().getContentType());
assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
}
@@ -198,4 +200,79 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
assertFalse(result.hasBody());
}
@Test
public void jsonPostForObject() throws URISyntaxException {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
MySampleBean bean = new MySampleBean();
bean.setWith1("with");
bean.setWith2("with");
bean.setWithout("without");
HttpEntity<MySampleBean> entity = new HttpEntity<MySampleBean>(bean, entityHeaders);
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
assertTrue(s.contains("\"with1\":\"with\""));
assertTrue(s.contains("\"with2\":\"with\""));
assertTrue(s.contains("\"without\":\"without\""));
}
@Test
public void jsonPostForObjectWithJacksonView() throws URISyntaxException {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
MySampleBean bean = new MySampleBean("with", "with", "without");
MappingJacksonValueHolder jsv = new MappingJacksonValueHolder(bean, MyJacksonView1.class);
HttpEntity<MappingJacksonValueHolder> entity = new HttpEntity<MappingJacksonValueHolder>(jsv);
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
assertTrue(s.contains("\"with1\":\"with\""));
assertFalse(s.contains("\"with2\":\"with\""));
assertTrue(s.contains("\"without\":\"without\""));
}
public interface MyJacksonView1 {};
public interface MyJacksonView2 {};
public static class MySampleBean {
@JsonView(MyJacksonView1.class)
private String with1;
@JsonView(MyJacksonView2.class)
private String with2;
private String without;
private MySampleBean() {
}
private MySampleBean(String with1, String with2, String without) {
this.with1 = with1;
this.with2 = with2;
this.without = without;
}
public String getWith1() {
return with1;
}
public void setWith1(String with1) {
this.with1 = with1;
}
public String getWith2() {
return with2;
}
public void setWith2(String with2) {
this.with2 = with2;
}
public String getWithout() {
return without;
}
public void setWithout(String without) {
this.without = without;
}
}
}