【问题标题】:Jackson deserialization error handlingJackson 反序列化错误处理
【发布时间】:2012-02-23 06:03:38
【问题描述】:

我的问题很简单:我有以下简单的类:

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

我正在尝试处理以下 JSON:

{
  "id": "blah"
}

显然这里有问题(“blah”不能解析为int)

以前,Jackson 抛出类似 org.codehaus.jackson.map.JsonMappingException:无法从字符串值“blah”构造 java.lang.Integer 的实例:不是有效的整数值

我同意这一点,但我想在某处注册一些内容,以允许忽略此类映射错误。 我尝试使用已注册的 DeserializationProblemHandler(请参阅 here),但它似乎只适用于未知属性而不是反序列化问题。

你对这个问题有任何线索吗?

【问题讨论】:

  • 为什么要忽略这个错误?我会将400 的HTTP 代码返回给每个尝试PUT 我这样的资源表示的客户端:)
  • 我将 Jackson 与 Spring MVC 和 bean 验证一起使用。问题是杰克逊在我到达 spring mvc 层之前抱怨反序列化问题..所以我不能以一致的方式向我的客户发送错误。
  • 我也经常使用Jackson来将一个对象可读转储到日志中。能够注意到序列化问题并继续前进非常有帮助

标签: java json deserialization jackson


【解决方案1】:

感谢Tatu from Jackson ML,我成功解决了我的问题。

对于 Jackson 中处理的每个原始类型,我都必须使用自定义的非阻塞反序列化器。 像这样的工厂:

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;
    }
}

然后以这种方式调用它(仅作为反序列化器传递您希望非阻塞的那些):

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();

【讨论】:

    【解决方案2】:

    您可能希望通过添加处理此特定异常的方法来让您的控制器处理问题

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

    当然是行

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

    在某些情况下可能会抛出类转换异常,但我还没有遇到过。

    【讨论】:

    • 你是对的,这可能是一个有效的解决方案。尽管如此,它也有一些缺点: - 您将一一报告错误(您需要 3 个请求来确定您有 3 个格式错误的字段)。当字段格式错误时放置空值允许我在字段上放置一个 \@NotNull 注释,以便在 1 个请求中获得详尽的错误列表 - 这允许对每个字段进行逐个处理。如果某些字段格式错误并且这并不重要,我不会在这些字段上放置 \@NotNull => 错误将被过滤
    • 是的,你是对的,你的方法更好。我对你如何使它适用于原始值感兴趣,因为我希望我的域模型具有 private int age,而不是 private Integer age。我设法通过使用 new StdDeserializer.IntegerDeserializer(int.class, 0), 并用 return delegate.getNullValue( );.如果您认为这是正确的解决方案,请更新您的代码以包含它,如果您有其他方法,请分享。
    • 我想知道在这种情况下考虑原始类型是否是个好主意。因为使用某些值来表示“非法”值对我来说听起来很奇怪(在您的情况下,该值似乎为 0)。我宁愿使用较少使用的值,例如 Integer.MAX_VALUE,但即使在这种情况下,我也觉得这种行为有点过于武断(这是个人 POV)
    • 对if(ex.getCause() instanceof JsonMappingException) 的简单检查(如果没有,则重新抛出或委托处理其他异常类型的适当逻辑)消除了获得ClassCastException 的任何可能性。否则,有用的答案
    【解决方案3】:

    我编写了一个简单的错误处理程序,它会给你一些错误,你可以将错误的请求作为状态码返回给用户。使用 @JsonProperty required = true 来获取与缺少属性相关的错误。杰克逊版本 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);
        }
    }
    

    并像这样在全局建议中调用此类对象

    @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);
        }
    

    【讨论】:

      【解决方案4】:

      创建一个简单的映射器:

      @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();
      }
      

      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多