Jackson deserialization error handling

后端 未结 4 1773
情话喂你
情话喂你 2020-12-13 10:15

My problem is fairly simple : I have the following simple class:

public class Foo {
   private int id = -1;
   public void setId(int _id){ this.id = _id; }
          


        
相关标签:
4条回答
  • 2020-12-13 10:52

    Create a simple Mapper:

    @Provider
    @Produces(MediaType.APPLICATION_JSON)
    public class JSONProcessingErroMapper implements ExceptionMapper<InvalidFormatException> {
    
    @Override
    public Response toResponse(InvalidFormatException ex) { 
        return Response.status(400)
                 .entity(new ClientError("[User friendly message]"))
                 .type(MediaType.APPLICATION_JSON)
                 .build();
    }
    

    }

    0 讨论(0)
  • 2020-12-13 11:02

    You might want to let your controller handle the problem by adding a method that handles this specific exception

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public String handleHttpMessageNotReadableException(HttpMessageNotReadableException ex)
    {
        JsonMappingException jme = (JsonMappingException) ex.getCause();
        return jme.getPath().get(0).getFieldName() + " invalid";
    }
    

    Of course, the line

        JsonMappingException jme = (JsonMappingException) ex.getCause();
    

    might throw a class cast exception for some cases but i haven't encountered them yet.

    0 讨论(0)
  • 2020-12-13 11:05

    I have written a simple error handler which will give you some kind of error which you can return to user with bad request as status code. Use @JsonProperty required = true to get error related to missing properties. Jackson version 2.9.8.

    public class JacksonExceptionHandler {
    
        public String getErrorMessage(HttpMessageNotReadableException e) {
            String message = null;
            boolean handled = false;
            Throwable cause = e.getRootCause();
    
            if (cause instanceof UnrecognizedPropertyException) {
                UnrecognizedPropertyException exception = (UnrecognizedPropertyException) cause;
                message = handleUnrecognizedPropertyException(exception);
                handled = true;
            }
            if (cause instanceof InvalidFormatException) {
                InvalidFormatException exception = (InvalidFormatException) cause;
                message = handleInvalidFormatException(exception);
                handled = true;
            }
            if (cause instanceof MismatchedInputException) {
                if (!handled) {
                    MismatchedInputException exception = (MismatchedInputException) cause;
                    message = handleMisMatchInputException(exception);
                }
            }
            if (cause instanceof JsonParseException) {
                message = "Malformed json";
            }
            return message;
        }
    
        private String handleInvalidFormatException(InvalidFormatException exception) {
            String reference = null;
            if (!exception.getPath().isEmpty()) {
                String path = extractPropertyReference(exception.getPath());
                reference = removeLastCharacter(path);
            }
            Object value = exception.getValue();
            return "Invalid value '" + value + "' for property : " + reference;
        }
    
        private String handleUnrecognizedPropertyException(UnrecognizedPropertyException exception) {
            String reference = null;
            if (!exception.getPath().isEmpty()) {
                String path = extractPropertyReference(exception.getPath());
                reference = removeLastCharacter(path);
            }
            return "Unknown property : '" + reference + "'";
        }
    
        private String handleMisMatchInputException(MismatchedInputException exception) {
            String reference = null;
            if (!exception.getPath().isEmpty()) {
                reference = extractPropertyReference(exception.getPath());
            }
            String property = StringUtils.substringBetween(exception.getLocalizedMessage(), "'", "'");
            // if property missing inside nested object
            if (reference != null && property!=null) {
                return "Missing property : '" + reference + property + "'";
            }
            // if invalid value given to array
            if(property==null){
                return "Invalid values at : '"+ reference +"'";
            }
            // if property missing at root level
            else return "Missing property : '" + property + "'";
        }
    
        // extract nested object name for which property is missing
        private String extractPropertyReference(List<JsonMappingException.Reference> path) {
            StringBuilder stringBuilder = new StringBuilder();
            path.forEach(reference -> {
                        if(reference.getFieldName() != null) {
                            stringBuilder.append(reference.getFieldName()).append(".");
                            // if field is null means it is array
                        } else stringBuilder.append("[].");
                    }
                    );
            return stringBuilder.toString();
        }
    
        // remove '.' at the end of property path reference
        private String removeLastCharacter(String string) {
            return string.substring(0, string.length() - 1);
        }
    }
    

    and call this class object in global advice like this

    @Override
        protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
            String message = new JacksonExceptionHandler().generator.getErrorMessage(ex);
            if(message == null){
                return ResponseEntity.badRequest().body("Malformed json");
            }
            return ResponseEntity.badRequest().body(message);
        }
    
    0 讨论(0)
  • 2020-12-13 11:10

    I succeeded to solve my problem, thanks to Tatu from Jackson ML.

    I had to use custom non blocking deserializers for every primitive types handled in Jackson. Something like this factory :

    public class JacksonNonBlockingObjectMapperFactory {
    
        /**
         * Deserializer that won't block if value parsing doesn't match with target type
         * @param <T> Handled type
         */
        private static class NonBlockingDeserializer<T> extends JsonDeserializer<T> {
            private StdDeserializer<T> delegate;
    
            public NonBlockingDeserializer(StdDeserializer<T> _delegate){
                this.delegate = _delegate;
            }
    
            @Override
            public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
                try {
                    return delegate.deserialize(jp, ctxt);
                }catch (JsonMappingException e){
                    // If a JSON Mapping occurs, simply returning null instead of blocking things
                    return null;
                }
            }
        }
    
        private List<StdDeserializer> jsonDeserializers = new ArrayList<StdDeserializer>();
    
        public ObjectMapper createObjectMapper(){
            ObjectMapper objectMapper = new ObjectMapper();
    
            SimpleModule customJacksonModule = new SimpleModule("customJacksonModule", new Version(1, 0, 0, null));
            for(StdDeserializer jsonDeserializer : jsonDeserializers){
                // Wrapping given deserializers with NonBlockingDeserializer
                customJacksonModule.addDeserializer(jsonDeserializer.getValueClass(), new NonBlockingDeserializer(jsonDeserializer));
            }
    
            objectMapper.registerModule(customJacksonModule);
            return objectMapper;
        }
    
        public JacksonNonBlockingObjectMapperFactory setJsonDeserializers(List<StdDeserializer> _jsonDeserializers){
            this.jsonDeserializers = _jsonDeserializers;
            return this;
        }
    }
    

    Then calling it like this way (pass as deserializers only those you want to be non blocking) :

    JacksonNonBlockingObjectMapperFactory factory = new JacksonNonBlockingObjectMapperFactory();
    factory.setJsonDeserializers(Arrays.asList(new StdDeserializer[]{
        // StdDeserializer, here, comes from Jackson (org.codehaus.jackson.map.deser.StdDeserializer)
        new StdDeserializer.ShortDeserializer(Short.class, null),
        new StdDeserializer.IntegerDeserializer(Integer.class, null),
        new StdDeserializer.CharacterDeserializer(Character.class, null),
        new StdDeserializer.LongDeserializer(Long.class, null),
        new StdDeserializer.FloatDeserializer(Float.class, null),
        new StdDeserializer.DoubleDeserializer(Double.class, null),
        new StdDeserializer.NumberDeserializer(),
        new StdDeserializer.BigDecimalDeserializer(),
        new StdDeserializer.BigIntegerDeserializer(),
        new StdDeserializer.CalendarDeserializer()
    }));
    ObjectMapper om = factory.createObjectMapper();
    
    0 讨论(0)
提交回复
热议问题