Select JsonView in the Spring MVC Controller

前端 未结 4 1397
故里飘歌
故里飘歌 2020-12-25 09:10

I\'m currently writing a REST api using Jackson (2.4.0-rc3) and spring mvc (4.0.3), and I\'m trying to make it secure.

In this way, I try to use JsonView to select

相关标签:
4条回答
  • 2020-12-25 09:37

    This works great :

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public void getZone(@PathVariable long id, @RequestParam(name = "tree", required = false) boolean withChildren, HttpServletResponse response) throws IOException {
        LOGGER.debug("Get a specific zone with id {}", id);
    
        Zone zone = zoneService.findById(id);
    
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        if (withChildren) {
            response.getWriter().append(mapper.writeValueAsString(zone));
        } else {
            response.getWriter().append(mapper.writerWithView(View.ZoneWithoutChildren.class).writeValueAsString(zone));
        }
    
    }
    
    0 讨论(0)
  • 2020-12-25 09:39

    I've solved my problem extending ResponseEntity like this :

    public class ResponseViewEntity<T> extends ResponseEntity<ContainerViewEntity<T>> {
    
        private Class<? extends BaseView> view;
    
        public ResponseViewEntity(HttpStatus statusCode) {
            super(statusCode);
        }
    
        public ResponseViewEntity(T body, HttpStatus statusCode) {
            super(new ContainerViewEntity<T>(body, BaseView.class), statusCode);
        }
    
        public ResponseViewEntity(T body, Class<? extends BaseView> view, HttpStatus statusCode) {
            super(new ContainerViewEntity<T>(body, view), statusCode);
        }
    
    }
    

    and ContainerViewEntity encapsulate the object and the selected view

    public class ContainerViewEntity<T> {
    
        private final T object;
        private final Class<? extends BaseView> view;
    
        public ContainerViewEntity(T object, Class<? extends BaseView> view) {
            this.object = object;
            this.view = view;
        }
    
        public T getObject() {
            return object;
        }
    
        public Class<? extends BaseView> getView() {
            return view;
        }
    
        public boolean hasView() {
            return this.getView() != null;
        }
    }
    

    After that, we have convert only the object with the good view.

    public class JsonViewMessageConverter extends MappingJackson2HttpMessageConverter {
    
        @Override
        protected void writeInternal(Object object, HttpOutputMessage outputMessage)
                throws IOException, HttpMessageNotWritableException {
            if (object instanceof ContainerViewEntity && ((ContainerViewEntity) object).hasView()) {
                writeView((ContainerViewEntity) object, outputMessage);
            } else {
                super.writeInternal(object, outputMessage);
            }
        }
    
        protected void writeView(ContainerViewEntity view, HttpOutputMessage outputMessage)
                throws IOException, HttpMessageNotWritableException {
            JsonEncoding encoding = this.getJsonEncoding(outputMessage.getHeaders().getContentType());
            ObjectWriter writer = this.getWriterForView(view.getView());
            JsonGenerator jsonGenerator = writer.getFactory().createGenerator(outputMessage.getBody(), encoding);
            try {
                writer.writeValue(jsonGenerator, view.getObject());
            } catch (IOException ex) {
                throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
            }
        }
    
        private ObjectWriter getWriterForView(Class<?> view) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
            return mapper.writer().withView(view);
        }
    
    }
    

    And to finish, I enable the converter

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="wc.handler.view.JsonViewMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>
    

    And that's it, I can select the View in the controller

    @Override
    @RequestMapping(value = "/get_by_id/{accountId}", method = RequestMethod.GET, headers = "Accept=application/json")
    public ResponseViewEntity<Account> getById(@PathVariable(value = "accountId") Long accountId) throws ServiceException {
        return new ResponseViewEntity<Account>(this.getAccountSrv().getById(accountId), PublicView.class, HttpStatus.OK);
    }
    
    0 讨论(0)
  • 2020-12-25 09:57

    FYI, Spring 4.1 already supported using @JsonView directly on @ResponseBody and ResponseEntity:

    Jackson’s @JsonView is supported directly on @ResponseBody and ResponseEntity controller methods for serializing different amounts of detail for the same POJO (e.g. summary vs. detail page). This is also supported with View-based rendering by adding the serialization view type as a model attribute under a special key.

    And in http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview you can find the much simpler solution:

    @RestController
    public class UserController {
    
        @RequestMapping(value = "/user", method = RequestMethod.GET)
        @JsonView(User.WithoutPasswordView.class)
        public User getUser() {
            return new User("eric", "7!jd#h23");
        }
    }
    
    public class User {
    
        public interface WithoutPasswordView {};
        public interface WithPasswordView extends WithoutPasswordView {};
    
        private String username;
        private String password;
    
        public User() {
        }
    
        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }
    
        @JsonView(WithoutPasswordView.class)
        public String getUsername() {
            return this.username;
        }
    
        @JsonView(WithPasswordView.class)
        public String getPassword() {
            return this.password;
        }
    }
    
    0 讨论(0)
  • 2020-12-25 10:01

    I really like the solution presented here to dynamically select a json view inside your controller method.

    Basically, you return a MappingJacksonValue which you construct with the value you want to return. After that you call setSerializationView(viewClass) with the proper view class. In my use case, I returned a different view depending on the current user, something like this:

    @RequestMapping("/foos")
    public MappingJacksonValue getFoo(@AuthenticationPrincipal UserDetails userDetails ) {
      MappingJacksonValue value = new MappingJacksonValue( fooService.getAll() );
      if( userDetails.isAdminUser() ) {
        value.setSerializationView( Views.AdminView.class );
      } else {
        value.setSerializationView( Views.UserView.class );
      }
      return value;
    }
    

    BTW: If you are using Spring Boot, you can control if properties that have no view associated are serialized or not by setting this in your application.properties:

    spring.jackson.mapper.default_view_inclusion=true
    
    0 讨论(0)
提交回复
热议问题