繁体   English   中英

当反序列化映射失败时,如何让杰克逊按原样抛出异常

[英]How to make Jackson throw exception as is when deserialization mapping fail

Jackson在处理反序列化映射期间发生的异常时有一个奇怪的行为:它抛出一个JsonMappingException.getCause()返回异常链的最内层。

//in main
ObjectMapper jsonMapper = new ObjectMapper();
String json = "{\"id\": 1}";
try {
    Q q = jsonMapper.readValue(json, Q.class);
} catch (JsonMappingException e) {
    System.out.println(e.getCause()); //java.lang.RuntimeException: ex 2
}

//class Q
public class Q {
    @JsonCreator
    public Q(@JsonProperty("id") int id) {
        throw new RuntimeException("ex 0", 
            new RuntimeException("ex 1", 
                new RuntimeException("ex 2")));
    }
}

在上面的代码中,我使用jsonMapper.readValue(..)将 String json映射到类Q的实例,其构造函数标记为@JsonCreator ,抛出一个RuntimeException链: "ex 0", "ex 1", "ex 2" 当映射失败时,我期望System.out.println(e.getCause()); 打印出ex 0 ,但它打印出ex 2

为什么杰克逊决定这样做,有没有办法配置它以便它不会丢弃我的ex 0 我试过了

jsonMapper.configure(DeserializationFeature.WRAP_EXCEPTIONS, false);

但它没有做任何事情。

在 Jackson 的StdValueInstantiator内部,当反序列化期间抛出异常时,此方法会被命中:

protected JsonMappingException wrapException(Throwable t)
{
    while (t.getCause() != null) {
        t = t.getCause();
    }
    if (t instanceof JsonMappingException) {
        return (JsonMappingException) t;
    }
    return new JsonMappingException("Instantiation of "+getValueTypeDesc()+" value failed: "+t.getMessage(), t);
}

如您所见,这将遍历嵌套运行时异常的每个“级别”,并将它命中的最后一个设置为它返回的JsonMappingException的原因。

这是我需要的代码:

  1. ObjectMapper注册一个新模块。

     @Test public void testJackson() { ObjectMapper jsonMapper = new ObjectMapper(); jsonMapper.registerModule(new MyModule(jsonMapper.getDeserializationConfig())); String json = "{\\"id\\": \\"1\\"}"; try { Q q = jsonMapper.readValue(json, Q.class); System.out.println(q.getId()); } catch (JsonMappingException e) { System.out.println(e.getCause()); //java.lang.RuntimeException: ex 2 } catch (JsonParseException e) { } catch (IOException e) { } }
  2. 创建自定义模块类。

     public class MyModule extends SimpleModule { public MyModule(DeserializationConfig deserializationConfig) { super("MyModule", ModuleVersion.instance.version()); addValueInstantiator(Q.class, new MyValueInstantiator(deserializationConfig, Q.class)); addDeserializer(Q.class, new CustomDeserializer()); } }
  3. 创建一个自定义ValueInstantiator类来覆盖wrapException(...) 将实例化器添加到模块中。

     public class MyValueInstantiator extends StdValueInstantiator { public MyValueInstantiator(DeserializationConfig config, Class<?> valueType) { super(config, valueType); } @Override protected JsonMappingException wrapException(Throwable t) { if (t instanceof JsonMappingException) { return (JsonMappingException) t; } return new JsonMappingException("Instantiation of "+getValueTypeDesc()+" value failed: "+t.getMessage(), t); } }
  4. 创建自定义解串器以使模块正常工作。 还将此类添加到模块初始化中。

     public class CustomDeserializer extends StdScalarDeserializer<Q> { public CustomDeserializer() { super(Q.class); } @Override public Q deserialize(JsonParser jp, DeserializationContext context) throws IOException { JsonNode node = jp.getCodec().readTree(jp); return new Q(node.get("id").asText()); } @Override public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer) throws IOException { return deserialize(jp, ctxt); } }

对于寻找不同解决方案的人来说,这对我在 Spring Boot 2.2.8.RELEASE 上有用。 注意:这是示例,当您有一个带有枚举的请求正文的休息控制器并且客户端可能发送错误的字段字符串性别并且您想要提供正确的错误消息时:

@PostMapping
public ResponseEntity<ProfileResponse> updateProfile(@RequestBody @Valid ProfileRequest profileRequest) {
    
    ProfileResponse profile = //do something important here that returns profile object response
    return ResponseEntity
            .status(HttpStatus.OK)
            .body(profile);
}

ProfileRequest 看起来像

@Data //Lombok getters and setters
public class ProfileRequest{
    private GenderEnum gender;
    //some more attributes
}

将此属性添加到 aplication.properties 文件中,以确保我们的自定义异常 GlobalRuntimeException(定义见后文)没有包含在 JsonMappingException 异常中。

spring.jackson.deserialization.WRAP_EXCEPTIONS=false

然后创建一个类,spring boot 将为其自动创建一个 bean(这将用于反序列化枚举类型的字段性别)。 如果我们没有找到枚举,那么我们知道抛出一个错误。

@JsonComponent
public class GenderEnumDeserializer extends JsonDeserializer<GenderEnum> {

    @Override
    public GenderEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String val = p.getValueAsString();
        GenderEnum genderEnum = GenderEnum.fromName(val);
        if(genderEnum == null){
            throw new GlobalRuntimeException("Invalid gender provided. Valid values are MALE | FEMALE | OTHER");
        }
        return genderEnum;
    }

}

GenderEnum 中的“forName”方法如下所示。

public static GenderEnum fromName(String name) {
    GenderEnum foundGenderEnum = null;
    for (GenderEnum genderEnum : values()) {
        if (genderEnum.name().equalsIgnoreCase(name)) {
            foundGenderEnum = genderEnum;
        }
    }
    return foundGenderEnum;
}

然后我们将在 ControllerAdvice 中设置捕获 GlobalRuntimeException:

@ResponseBody
@ExceptionHandler(GlobalRuntimeException.class)
ResponseEntity<?> handleInvalidGlobalRuntimeException(HttpServletRequest request, GlobalRuntimeException ex) {
    LOGGER.error("Error " + ex);
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(new ErrorMessage(ex.getCustomMessage));
}

就是这样。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM