杰克逊在失踪财产的反序列化期间抛出NPE

时间:2018-03-02 13:42:02

标签: 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"
}

和execption:

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值而不进行明确检查?
  3. 编辑:添加了一些代码示例

    让我们说这是我的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;
    }
    

    很棒,它不需要改变其他任何东西(在我的mapper类中)。但假设我得到了这种json

     {
       "firstName": "name"
    }
    

    现在它抛出了NPE。因此,我们的想法是检查mapper中的值是否为null。我们来做吧:

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

    }

    问题是,在这种情况下,我必须更改我的mapper类,因为第4个参数未被处理。

1 个答案:

答案 0 :(得分:1)

此行返回null

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

因为该属性显然未在JSON中定义。您可以通过多种方式解决问题,但在使用get(name)和其中一个方法asLong,asString等时,您将始终必须检查null,或者只检查属性是否使用has(name)定义。

您还可以使用辅助功能

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

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

配置对象映射器时,您可以使用configure方法指定它应该有多严格,可以使用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中可以看到的使用final属性,当你使用它们时,你必须创建一个带参数的构造函数,并且不能创建一个空的构造函数(至少事先没有指定它的值) ,这个空构造函数对于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