为什么将Jackson设置为将空列表视为null不起作用?

huangapple go评论89阅读模式
英文:

Why Doesn't Setting Jackson to Accept Empty Lists as Null Work?

问题

我的公司有一个使用Spring Web的Java Web服务,通过REST API接受JSON。我们正在使用Maven,我们的Jackson版本是2.9。我们试图防止在集成方在API不期望空列表的情况下传递空列表时抛出反序列化异常。

例如,这是我的应用程序类:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@EnableWebMvc
@SpringBootApplication
public class ExampleApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

Student.java类:

import lombok.Data;
import java.util.Map;

@Data
public class Student {

    private String firstName;
    private String lastName;
    private Map<String, String> classGrades;
}

一个StudentController:

package com.example.example.controllers;

import com.example.example.models.Student;
import org.springframework.http.RequestEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.stream.Stream;

@RestController()
@RequestMapping(value = "/Students/", produces = "application/json")
public class StudentController {

    @PostMapping(path = "")
    public RequestEntity<Student> createNewStudent(@RequestBody Student student) {

       return null;
    }
}

application.properties文件:

spring.jackson.deserialization.accept-empty-array-as-null-object=true

pom.xml包含所有默认依赖项,添加了以下内容:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>

其他项目文件与Spring初始化器生成的项目结构保持一致。预期的请求正文(使用Postman生成的JSON):

{
  "firstName": "Mark",
  "lastName": "Twain",
  "classGrades": {
    
  }
}

运行得很好。然而,如果任何字段(尽管在我们的特定情况下是classGrades字段)收到一个空列表,将会抛出一个Jackson反序列化异常。一个失败的示例JSON请求:

{
  "firstName": "Mark",
  "lastName": "Twain",
  "classGrades": []
}

抛出的异常如下:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token
 at [Source: (PushbackInputStream); line: 65, column: 28] 

根据项目的GitHub页面,选项ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT应该可以解决这个问题。我们已经尝试了在ObjectMapper配置对象上直接设置此选项,以及在application.properties文件中使用以下行:

spring.jackson.deserialization.accept-empty-array-as-null-object=true

但是,两种方法似乎都没有生效。我们目前使用一个在可能出现此问题的字段上使用@JsonDeserialize(using = MyCustomDeserializer.class)注解的变通方法。然而,我们希望能够默认情况下将所有字段视为空列表为空。我们是否误解了配置选项并且使用方法不正确?是否有一种方法在我们的应用程序中将空列表视为null?如果有,我们如何实现这一点?

英文:

My company has a Java web service using Spring Web that accepts JSON via a REST API. We're using Maven and our Jackson version is 2.9. We're trying to prevent deserialization exceptions from being thrown when an integrator passes in an empty list when our API isn't expecting one.

For example, here's my application class:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@EnableWebMvc
@SpringBootApplication
public class ExampleApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

Student.java class:

import lombok.Data;
import java.util.Map;

@Data
public class Student {

    private String firstName;
    private String lastName;
    private Map&lt;String, String&gt; classGrades;
}

A StudentController:

package com.example.example.controllers;

import com.example.example.models.Student;
import org.springframework.http.RequestEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.stream.Stream;

@RestController()
@RequestMapping(value = &quot;/Students/&quot;, produces = &quot;application/json&quot;)
public class StudentController {

    @PostMapping(path = &quot;&quot;)
    public RequestEntity&lt;Student&gt; createNewStudent(@RequestBody Student student) {

       return null;
    }
}

The application.properties:

spring.jackson.deserialization.accept-empty-array-as-null-object=true

The pom.xml contains all the default dependencies, with the following added:

&lt;dependency&gt;
    &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
    &lt;artifactId&gt;lombok&lt;/artifactId&gt;
    &lt;version&gt;1.18.10&lt;/version&gt;
&lt;/dependency&gt;

Every other project file is default to the project structure generated by the Spring initializer. The expected request body (JSON formed using Postman):

{
  &quot;firstName&quot;: &quot;Mark&quot;,
  &quot;lastName&quot;: &quot;Twain&quot;,
  &quot;classGrades&quot;: {
    
  }
}

Works just fine. However, if any field (though in our specific case, the classGrades field) receives an empty list, a Jackson deserialization exception is thrown. An example JSON request that fails:

{
  &quot;firstName&quot;: &quot;Mark&quot;,
  &quot;lastName&quot;: &quot;Twain&quot;,
  &quot;classGrades&quot;: []
}

And the exception that is thrown:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.LinkedHashMap` out of START_ARRAY token
 at [Source: (PushbackInputStream); line: 65, column: 28] 

According to the project's github page, the option ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT should resolve this issue for us. We've tried setting this directly on an ObjectMapper configuration object and within application.properties using the line:

spring.jackson.deserialization.accept-empty-array-as-null-object=true

Neither method seemed to take effect. We're currently using a workaround using the @JsonDeserialize(using = MyCustomDeserializer.class) annotaion on fields prone to this problem. However, we would like to be able to have all of our fields treat empty lists as null by default.

Are we misunderstanding the configuration option and using it incorrectly? Is there a way to treat empty lists as null within our app, and if so, how can we accomplish this?

答案1

得分: 2

在这个示例案例中,我的问题出现在 ExampleApplication 类中的 @EnableWebMvc 注解。移除该注解后,我成功地将一个空数组发送到我的端点,然后该端点将其视为一个空对象。

注意

即使在移除注解后,我在公司的应用程序中仍然存在最初的问题。然而,这似乎可能是与另一个设置有关的问题,该设置可能与 ...accept-empty-arrays-as-null-object 冲突。

英文:

For this example case, my problem was the @EnableWebMvc annotation within the ExampleApplication class. Removing that annotation allowed me to successfully send an empty array to my endpoint, which then received it as a null object.

Note

My original problem still exists within my company's application, even after removing the annotation. However, it seems like this may be an issue with a different setting that might be clashing with ...accept-empty-arrays-as-null-object.

答案2

得分: -1

一个 Map<String, String> 被序列化为

    { "k1": "v1", "k2": "v2" }

一个空的 Map<String, String> 被序列化为

    { }

在你的示例中,你尝试将一个代表空列表或数组的 [] 放入一个 Map<String, String> 中,这就是为什么 Jackson 报错的原因。简单来说,不能将一个空列表映射到一个 Map 中。
这也解释了为什么启用 ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT 属性没有效果。

## 更新
我之前是错误的。Jackson 可以将空数组映射到一个 Map 中。
创建了一个最小的 Spring Boot 示例,使用了 **spring.jackson.deserialization.accept-empty-array-as-null-object=true** 属性,可以将空数组 [] 映射到 Map 中。

如果你想要查看,可以点击[这里][1]。

  [1]: https://github.com/routis/jackson-demo/blob/master/src/test/java/routis/examples/jacksondemo/JacksonDemoApplicationTests.java
英文:

A Map<String,String> is serialized as

{ &quot;k1&quot;: &quot;v1&quot;, &quot;k2&quot;: &quot;v2&quot;}

An empty Map<String,String> is serialized as

{ }

In your example you try to put a [], which represents an empty list or array, into a Map<String,String> and that is why Jackson complains. Simply, cannot map an empty list into a Map.
This also explains why enabling ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT has no effect

##Update
I was not right. Jackson can map empty array to a Map.
Created a minimal springboot example that uses the spring.jackson.deserialization.accept-empty-array-as-null-object=true property and can map an empty array [] to the Map

Check here if you want.

huangapple
  • 本文由 发表于 2020年4月9日 05:07:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/61110036.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定