java - Jackson 在对丢失的属性(property)进行反序列化期间抛出 NPE

标签 java json spring-boot jackson

我在尝试反序列化 JSON 时仍然遇到 NPE。如果属性丢失,我希望设置默认值(0/null)。这是我的 Spring Boot 配置 bean:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
    jsonConverter.setObjectMapper(objectMapper());
    return jsonConverter;
}

ObjectMapper objectMapper() {
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addDeserializer(ProductBasicDto.class, new ProductDeserializer());

    ObjectMapper mapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
            .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false)
            .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);

    mapper.registerModule(simpleModule);
    return mapper;
}

我的自定义解串器:

@Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    JsonNode node = jsonParser.getCodec().readTree(jsonParser);
    System.out.println(node.get("id").asLong()); // here it throws NPE
 return null; // ignore that, just for testing
}

json:

{
  "name": "js",
  "category": "CoNiFer"
}

和执行:

java.lang.NullPointerException: null
at api.product.infrastructure.ProductDeserializer.deserialize(ProductDeserializer.java:19)
at api.product.infrastructure.ProductDeserializer.deserialize(ProductDeserializer.java:14)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:235)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:223)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:206)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:157)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
  1. 如何避免此 NPE?
  2. 如果属性(如本例中的 id)丢失,是否可以在解串器中不进行显式检查的情况下放入 0/null 值?

编辑:添加了一些代码示例

假设这是我的 DTO 类:

class MyDto implements Serializable {
private final String firstName;
private final String lastName;
}

现在我正在创建我的自定义映射器:

@Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser, 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
 objectMapper.convertValue(jsonNode, MyDto.class);

...

现在我决定向 MyDto 添加额外的 Integer:

    class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
}

这很棒,不需要更改任何其他内容(在我的映射器类中)。但假设我得到了这种 json

 {
   "firstName": "name"
}

现在它会抛出 NPE。所以想法是检查映射器中的值是否为空。让我们开始吧:

@Override
public ProductBasicDtoWrapper deserialize(JsonParser jsonParser, 
DeserializationContext deserializationContext) throws IOException, 
JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
  node.get("firstName") == null ?
  node.get("lastName") == null ?
  node.get("age") == null ?

}

好吧,现在可以了,现在假设我向 DTO 类添加了一个属性

class MyDto implements Serializable {
private final String firstName;
private final String lastName;
private final Integer age;
private final String card;

}

问题是,在这种情况下,我还必须更改我的映射器类,因为未处理第四个参数。

最佳答案

该行返回 null

node.get("id") // returns null when property is not defined

因为该属性显然没有在 JSON 中定义。您可以通过多种方式解决您的问题,但在使用 get(name) 以及 asLong、asString 等方法之一时,您始终必须检查 null,或者仅检查该属性是否定义为有(名称)

您还可以使用辅助函数

public static Long getLong(JsonNode node, String name) {
    if (node.get(name) != null && node.get(name).isLong()) {
        return node.get(name).asLong();
    } else {
        return null;
    }
}

你可以返回null,或者抛出异常,如果返回null你应该小心,稍后再处理。

然后用它来打印变量或未定义时为 null。

System.out.println(getLong(node, "id"));

编辑(根据编辑的问题):

当您配置对象映射器时,您可以使用配置方法指定它的严格程度,您可以使用 DeserializationFeature 枚举来指示它何时应该失败以及何时不应该失败。

在这里您可以看到每个功能及其用途: https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/DeserializationFeature.html

现在,如果类中的属性名称和 json 匹配,您可以将 json 转换为 dto 对象,如下所示:

ObjectMapper mapper = new ObjectMapper() // object mapper with wanted properties
    .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, 

mapper.readValue(json, MyDto.class);

这里您只需要创建映射器(不需要反序列化器),然后将其转换为目标类。

如果类和 json 中有不同的名称,则必须使用注释 @JsonProperty 并指定名称。

可能出现的问题:

现在,我在 DTO 中看到您使用了最终属性,当您使用它们时,您必须创建一个带参数的构造函数,并且不能创建一个空构造函数(至少没有事先指定它的值),这个空构造函数对于 java POJO 是必需的,并且由对象映射器在内部使用。

所以你的 DTO 应该是这样的:

class MyDto implements Serializable {
    private String firstName;
    private String lastName;
    private Integer age;
    // empty constructor (only necessary when another constructor is specified)
    // getters and setters
}

如果您仍然需要使用 Immutable 对象而不是 POJO,您可以创建如下所示的类:

class MyDto implements Serializable {
    private final String firstName;
    private final String lastName;
    private final Integer age;
    @JsonCreator
    MyDto(@JsonProperty("firstName") String firstName,
          @JsonProperty("lastName") String lastName,
          @JsonProperty("age") Integer age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    // getters
}

使用映射器

使用上面的 POJO 类和对象映射器,您可以执行以下操作:

MyDto myDto1 = mapper.readValue("{\"firstName\": \"name\"}", MyDto.class); // object with firstName defined, lastName and age are null
MyDto myDto2 = mapper.readValue("{\"firstName\": \"name\",\"lastName\": \"last\"}", MyDto.class); // object with firstName and lastName defined, age is null
MyDto myDto3 = mapper.readValue("{\"firstName\": \"name\",\"lastName\": \"last\",\"age\": 1}", MyDto.class); // object with firstName, lastName and age defined

您甚至可以使用空对象或具有未知属性。

MyDto myDto4 = mapper.readValue("{}", MyDto.class); // object with all properties null
MyDto myDto5 = mapper.readValue("{\"blablah\": \"name\"}", MyDto.class); // object with all properties null

关于java - Jackson 在对丢失的属性(property)进行反序列化期间抛出 NPE,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49070317/

相关文章:

java - CursorIndexOutOfBoundsException : Index 1 requested,,大小为 1。错误 - Android

java - 如何在没有 xml 的情况下配置 Ehcache 3 + spring boot + java config?

java - 将 Java 对象作为值保存到 Redis

c# - 使用多个文件发布 StreamContent

tomcat - 使用 Boot 刷新静态内容

java - 完全关闭 Java 应用程序

java - "Unnecessary cast to float"似乎是必要的。我错过了什么?

json - 如何通过 Google Places Autocomplete API 以多种语言执行搜索来获取城市的唯一标识符​​?

java - 设计模式(命令模式),以避免出现多个if条件

java - Spring Kafka ChainedKafkaTransactionManager 不与 JPA Spring-data 事务同步