Announcement Announcement Module
Collapse
No announcement yet.
How to get specific binding exception property path from JsonMappingException Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to get specific binding exception property path from JsonMappingException

    I'm using Spring 3.0.2 to try out the @Controller stuff for a JSON based REST API. Everything is going well, except when it comes to exception management during binding.

    I have a method which accepts JSON data:

    Code:
    @RequestMapping(value="/register", method = RequestMethod.POST)
    @ResponseStatus(value = HttpStatus.OK)
    void register(@RequestBody Registration registration);
    This works perfectly when the JSON received can actually be bound to the Registration object. However, when the payload contains type-mismatched data which can't even be bound (like setting a boolean value to an integer field), the server spews out a less than helpful 500 error with some useless Jackson mapping exception:

    Code:
    org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of int out of VALUE_FALSE token
     at [Source: org.apache.catalina.connector.CoyoteInputStream@25e7bff8; line: 1, column: 113]
    	org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:159)
    	org.codehaus.jackson.map.deser.StdDeserializationContext.mappingException(StdDeserializationContext.java:192)
    	org.codehaus.jackson.map.deser.StdDeserializer._parseInt(StdDeserializer.java:151)
    	org.codehaus.jackson.map.deser.StdDeserializer$IntegerDeserializer.deserialize(StdDeserializer.java:545)
    	org.codehaus.jackson.map.deser.StdDeserializer$IntegerDeserializer.deserialize(StdDeserializer.java:533)
    	org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:135)
    	org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:221)
    	org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:390)
    	org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:286)
    	org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:1588)
    	org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1158)
    	org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.readInternal(MappingJacksonHttpMessageConverter.java:111)
    	org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:152)
    	org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveRequestBody(HandlerMethodInvoker.java:552)
    	org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:283)
    	org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:163)
    	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:414)
    	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:402)
    	org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:771)
    
      ...
    I've debugged the Spring code, and the exception being thrown (JsonMappingException) doesn't contain any sort of path information to the property which failed to bind.

    How can I trap more granular and useful exception information in this case so I can respond appropriately? I'd like to treat these exceptions just like I do my ConstraintViolations: return a JSON map which has a list of field-level errors along with the exceptions associated with each field. Given what I seem to have, I am not sure how I'm going to get to the property path I need...

    How are other people handling this? I don't see any meaningful way to respond to the client.

  • #2
    Out of curiosity I implemented the ConversionService stuff to support Spring format annotations found in the mvc ajax samples:

    https://src.springframework.org/svn/...mvc-ajax/trunk

    What I noticed is that when deserialization flows through this (for stuff annotated with @NumberFormat, for example), it seems to tie in with ConversionService and bubble out exactly what I want from MappingJacksonHttpMessageConverter: a ConversionFailedException with full path information including the source/target types, failed binding value, reflective target field, etc.

    But, this only feeds stuff annotated with Spring format annotations. I am thinking I can probably adapt it somehow to just send EVERYTHING through some custom type converter, but this seems pretty insane. Surely there's some way to make the standard Jettison deserializer hook in to the ConversionService.

    Comment


    • #3
      So, I ended up subclassing BeanDeserializer and overriding the deserializeFromObject method. I am not sure why the Jackson default implementation doesn't do this, because it has access to the path information for the mapping failure; it simply chooses not to expose it. In case anybody cares, here is what I changed:

      (BeanDeserializer#deserializeFromObject)
      Code:
      if (prop != null) { // normal case
          prop.deserializeAndSet(jp, ctxt, bean);
          continue;
      }
      Notice how it does no exception handling for the deserialization. All I did was change it thusly:

      Code:
      if (prop != null) { // normal case
         try {
            prop.deserializeAndSet(jp, ctxt, bean);
         } catch (JsonMappingException e) {
            throw JsonMappingException.wrapWithPath(e, new JsonMappingException.Reference(this.getBeanClass(), prop.getPropertyName()));
         }
         continue;
      }
      And there you go. Now your JsonMappingExceptions actually have path information related to what failed. No idea why they aren't doing this in the standard impl, but it sure solves my problem...


      EDIT: Forgot to mention, I also had to subclass BeanDeserializerFactory#constructBeanDeserializerI nstance to provide my instance, and used a BeanPostProcessor to set the new factory onto the ObjectMapper that I manually create to override the stuff provided by <mvc:annotation-driven/>.

      Not gonna bother posting any more details unless somebody actually cares.
      Last edited by ironcladlou; May 15th, 2010, 12:06 PM. Reason: More information

      Comment


      • #4
        I've been playing with the same example these days. I'd like to get a little bit more info about your solution, if it is possible.

        1) What was the ConversionService implementation being used before you implemented your own and how was it configured? I noticed that it has been @Autowired at many places but I haven't really figured out where it has been declared.

        2) When the JsonMappingException is thrown which component at what point actually takes care of it?

        Comment


        • #5
          TO:- ironcladlou

          I am facing the same problem as yours, however I also have to catch the JsonMappingException and send JSON (error string) back to the client.
          I am trying to write interceptor around DispatcherServlet but want to know if there is a better way of doing this ?


          ~D

          Comment

          Working...
          X