├── JsonViewExample ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── mangofactory │ │ └── jsonview │ │ ├── BaseView.java │ │ ├── DataView.java │ │ ├── JsonViewSupportFactoryBean.java │ │ ├── PojoView.java │ │ ├── ResponseView.java │ │ ├── ViewAwareJsonMessageConverter.java │ │ ├── ViewInjectingReturnValueHandler.java │ │ └── example │ │ ├── Book.java │ │ └── controllers │ │ └── BookController.java │ └── webapp │ ├── WEB-INF │ ├── log4j.xml │ ├── spring-servlet.xml │ └── web.xml │ └── index.jsp └── README.md /JsonViewExample/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.mangofactory.jsonView 5 | JsonViewExample 6 | war 7 | 0.0.1-SNAPSHOT 8 | JsonViewExample 9 | 10 | 3.2.3.RELEASE 11 | 2.2.2 12 | 13 | 14 | 15 | com.springsource.repository.bundles.release 16 | SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases 17 | http://repository.springsource.com/maven/bundles/release 18 | 19 | 20 | sonatype 21 | Sonatype 22 | https://oss.sonatype.org/ 23 | 24 | 25 | com.springsource.repository.bundles.external 26 | SpringSource Enterprise Bundle Repository - External Bundle Releases 27 | http://repository.springsource.com/maven/bundles/external 28 | 29 | 30 | com.springsource.repository.libraries.release 31 | SpringSource Enterprise Bundle Repository - SpringSource Library Releases 32 | http://repository.springsource.com/maven/libraries/release 33 | 34 | 35 | spring-milestone 36 | Spring Maven MILESTONE Repository 37 | http://maven.springframework.org/milestone 38 | 39 | 40 | com.springsource.repository.libraries.external 41 | SpringSource Enterprise Bundle Repository - External Library Releases 42 | http://repository.springsource.com/maven/libraries/external 43 | 44 | 45 | com.springsource.repository.snapshot 46 | http://maven.springframework.org/snapshot/ 47 | 48 | 49 | com.mysema 50 | QueryDSL repo 51 | http://source.mysema.com/maven2/releases/ 52 | 53 | 54 | EclipseLink Repo 55 | 57 | http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo 58 | 59 | 60 | spring-maven-external 61 | Springframework Maven External Repository 62 | http://maven.springframework.org/external 63 | 64 | 65 | 66 | 67 | org.springframework 68 | org.springframework.spring-library 69 | libd 70 | ${spring.version} 71 | 72 | 73 | 74 | com.springsource.org.codehaus.jackson.mapper 75 | 76 | org.codehaus.jackson 77 | 78 | 79 | 80 | com.springsource.com.fasterxml.jackson.core.jackson-core 81 | 82 | com.fasterxml.jackson.core 83 | 84 | 85 | 86 | 87 | javax.servlet 88 | servlet-api 89 | 2.5 90 | 91 | 92 | org.slf4j 93 | slf4j-api 94 | 1.6.1 95 | 96 | 97 | org.slf4j 98 | slf4j-log4j12 99 | 1.6.1 100 | 101 | 102 | org.projectlombok 103 | lombok 104 | 0.11.4 105 | provided 106 | 107 | 108 | com.fasterxml.jackson.core 109 | jackson-core 110 | ${jackson.version} 111 | 112 | 113 | com.fasterxml.jackson.core 114 | jackson-databind 115 | ${jackson.version} 116 | 117 | 118 | com.fasterxml.jackson.core 119 | jackson-annotations 120 | ${jackson.version} 121 | 122 | 123 | commons-logging 124 | commons-logging 125 | 1.1.1 126 | 127 | 128 | 129 | 130 | com.google.guava 131 | guava 132 | 13.0.1 133 | 134 | 135 | http://martypitt.wordpress.com 136 | 137 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/BaseView.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | public interface BaseView { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/DataView.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | public interface DataView { 4 | boolean hasView(); 5 | Class getView(); 6 | Object getData(); 7 | } 8 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/JsonViewSupportFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | import java.util.List; 4 | 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.method.support.HandlerMethodReturnValueHandler; 10 | import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite; 11 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; 12 | import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; 13 | 14 | import com.google.common.collect.Lists; 15 | 16 | /** 17 | * Modified Spring 3.1's internal Return value handlers, and wires up a decorator 18 | * to add support for @JsonView 19 | * 20 | * @author martypitt 21 | * 22 | */ 23 | @Slf4j 24 | public class JsonViewSupportFactoryBean implements InitializingBean { 25 | 26 | @Autowired 27 | private RequestMappingHandlerAdapter adapter; 28 | @Override 29 | public void afterPropertiesSet() throws Exception { 30 | HandlerMethodReturnValueHandlerComposite returnValueHandlers = adapter.getReturnValueHandlers(); 31 | List handlers = Lists.newArrayList(returnValueHandlers.getHandlers()); 32 | decorateHandlers(handlers); 33 | adapter.setReturnValueHandlers(handlers); 34 | } 35 | private void decorateHandlers(List handlers) { 36 | for (HandlerMethodReturnValueHandler handler : handlers) { 37 | if (handler instanceof RequestResponseBodyMethodProcessor) 38 | { 39 | ViewInjectingReturnValueHandler decorator = new ViewInjectingReturnValueHandler(handler); 40 | int index = handlers.indexOf(handler); 41 | handlers.set(index, decorator); 42 | log.info("JsonView decorator support wired up"); 43 | break; 44 | } 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/PojoView.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class PojoView implements DataView { 7 | 8 | private final Object data; 9 | private final Class view; 10 | @Override 11 | public boolean hasView() { 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/ResponseView.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface ResponseView { 8 | public Class value(); 9 | } 10 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/ViewAwareJsonMessageConverter.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.http.HttpOutputMessage; 6 | import org.springframework.http.converter.HttpMessageNotWritableException; 7 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 8 | import com.fasterxml.jackson.core.JsonEncoding; 9 | import com.fasterxml.jackson.core.JsonGenerator; 10 | import com.fasterxml.jackson.databind.MapperFeature; 11 | import com.fasterxml.jackson.databind.ObjectMapper; 12 | import com.fasterxml.jackson.databind.ObjectWriter; 13 | 14 | /** 15 | * Adds support for Jackson's JsonView on methods 16 | * annotated with a {@link ResponseView} annotation 17 | * @author martypitt 18 | * 19 | */ 20 | public class ViewAwareJsonMessageConverter extends 21 | MappingJackson2HttpMessageConverter { 22 | 23 | public ViewAwareJsonMessageConverter() 24 | { 25 | super(); 26 | ObjectMapper defaultMapper = new ObjectMapper(); 27 | defaultMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); 28 | setObjectMapper(defaultMapper); 29 | } 30 | 31 | @Override 32 | protected void writeInternal(Object object, HttpOutputMessage outputMessage) 33 | throws IOException, HttpMessageNotWritableException { 34 | if (object instanceof DataView && ((DataView) object).hasView()) 35 | { 36 | writeView((DataView) object, outputMessage); 37 | } else { 38 | super.writeInternal(object, outputMessage); 39 | } 40 | } 41 | protected void writeView(DataView view, HttpOutputMessage outputMessage) 42 | throws IOException, HttpMessageNotWritableException { 43 | JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType()); 44 | ObjectWriter writer = getWriterForView(view.getView()); 45 | JsonGenerator jsonGenerator = 46 | writer.getFactory().createGenerator(outputMessage.getBody(), encoding); 47 | try { 48 | writer.writeValue(jsonGenerator, view.getData()); 49 | } 50 | catch (IOException ex) { 51 | throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); 52 | } 53 | } 54 | 55 | private ObjectWriter getWriterForView(Class view) { 56 | ObjectMapper mapper = new ObjectMapper(); 57 | mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false); 58 | return mapper.writer().withView(view); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/ViewInjectingReturnValueHandler.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview; 2 | 3 | import org.springframework.core.MethodParameter; 4 | import org.springframework.web.context.request.NativeWebRequest; 5 | import org.springframework.web.method.support.HandlerMethodReturnValueHandler; 6 | import org.springframework.web.method.support.ModelAndViewContainer; 7 | 8 | /** 9 | * Decorator that detects a declared {@link ResponseView}, and 10 | * injects support if required 11 | * @author martypitt 12 | * 13 | */ 14 | public class ViewInjectingReturnValueHandler implements 15 | HandlerMethodReturnValueHandler { 16 | 17 | private final HandlerMethodReturnValueHandler delegate; 18 | 19 | public ViewInjectingReturnValueHandler(HandlerMethodReturnValueHandler delegate) 20 | { 21 | this.delegate = delegate; 22 | } 23 | @Override 24 | public boolean supportsReturnType(MethodParameter returnType) { 25 | return delegate.supportsReturnType(returnType); 26 | } 27 | 28 | @Override 29 | public void handleReturnValue(Object returnValue, 30 | MethodParameter returnType, ModelAndViewContainer mavContainer, 31 | NativeWebRequest webRequest) throws Exception { 32 | 33 | Class viewClass = getDeclaredViewClass(returnType); 34 | if (viewClass != null) 35 | { 36 | returnValue = wrapResult(returnValue,viewClass); 37 | } 38 | 39 | delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest); 40 | } 41 | /** 42 | * Returns the view class declared on the method, if it exists. 43 | * Otherwise, returns null. 44 | * @param returnType 45 | * @return 46 | */ 47 | private Class getDeclaredViewClass(MethodParameter returnType) { 48 | ResponseView annotation = returnType.getMethodAnnotation(ResponseView.class); 49 | if (annotation != null) 50 | { 51 | return annotation.value(); 52 | } else { 53 | return null; 54 | } 55 | } 56 | private Object wrapResult(Object result, Class viewClass) { 57 | PojoView response = new PojoView(result, viewClass); 58 | return response; 59 | } 60 | } -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/example/Book.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview.example; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import com.fasterxml.jackson.annotation.JsonView; 8 | 9 | import com.mangofactory.jsonview.BaseView; 10 | 11 | @Data @NoArgsConstructor @AllArgsConstructor 12 | public class Book { 13 | 14 | @JsonView(SummaryView.class) 15 | private String title; 16 | @JsonView(SummaryView.class) 17 | private String author; 18 | private String review; 19 | @JsonView(BaseView.class) 20 | private Integer id; 21 | 22 | public static interface SummaryView extends BaseView {} 23 | 24 | } 25 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/java/com/mangofactory/jsonview/example/controllers/BookController.java: -------------------------------------------------------------------------------- 1 | package com.mangofactory.jsonview.example.controllers; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | import com.google.common.collect.Lists; 11 | import com.mangofactory.jsonview.ResponseView; 12 | import com.mangofactory.jsonview.example.Book; 13 | import com.mangofactory.jsonview.example.Book.SummaryView; 14 | 15 | @Controller 16 | public class BookController { 17 | 18 | List data; 19 | 20 | public BookController() 21 | { 22 | data = Lists.newArrayList( 23 | new Book("Effective Java","Joshua Bloch","Essential",1), 24 | new Book("Breaking Dawn","Stephanie Myers","Just terrible",2) 25 | ); 26 | } 27 | @RequestMapping("/books") 28 | public @ResponseBody List getBooks() 29 | { 30 | return data; 31 | } 32 | @RequestMapping("/books/summaries") 33 | @ResponseView(SummaryView.class) 34 | public @ResponseBody List getBookSummaries() 35 | { 36 | return data; 37 | } 38 | @RequestMapping("/book/{id}/summary") 39 | @ResponseView(SummaryView.class) 40 | public @ResponseBody Book getSummary(@PathVariable("id") Integer id) 41 | { 42 | return data.get(id - 1); 43 | } 44 | @RequestMapping("/book/{id}") 45 | public @ResponseBody Book getDetail(@PathVariable("id") Integer id) 46 | { 47 | return data.get(id - 1); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/webapp/WEB-INF/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/webapp/WEB-INF/spring-servlet.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | JsonView Example 7 | 8 | log4jConfigLocation 9 | /WEB-INF/log4j.xml 10 | 11 | 12 | org.springframework.web.util.Log4jConfigListener 13 | 14 | 15 | spring 16 | org.springframework.web.servlet.DispatcherServlet 17 | 1 18 | 19 | 20 | spring 21 | /* 22 | 23 | 24 | -------------------------------------------------------------------------------- /JsonViewExample/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello World!

4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JsonViewExample 2 | =============== 3 | 4 | Support for custom Jackson @JsonView within Spring MVC 5 | 6 | Allows per-method support of custom views within Spring MVC methods, by annotating with `@ResponseView`. 7 | 8 | For example: 9 | 10 | @RequestMapping("/books") 11 | public @ResponseBody List getBooks() 12 | { 13 | return data; 14 | } 15 | @RequestMapping("/books/summaries") 16 | @ResponseView(SummaryView.class) 17 | public @ResponseBody List getBookSummaries() 18 | { 19 | return data; 20 | } 21 | 22 | In the above scenario, calls to `/books` recieve the entire `Book` model, while calls to `/books/summaries` only recieve properties annotated with Jackson's `@JsonView(SummaryView.class)` annotation. 23 | 24 | See http://martypitt.wordpress.com/2012/11/05/custom-json-views-with-spring-mvc-and-jackson/ for details. 25 | 26 | --------------------------------------------------------------------------------