Jackson中DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT和ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

墨、魚發表於2020-11-03

Jackson使用過程中的一些疑惑和跟蹤。

DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

首先先來看看這個配置項對應的JavaDoc:

/**
     * Feature that can be enabled to allow JSON empty String
     * value ("") to be bound as `null` for POJOs and other structured
     * values ({@link java.util.Map}s, {@link java.util.Collection}s).
     * If disabled, standard POJOs can only be bound from JSON `null` or
     * JSON Object (standard meaning that no custom deserializers or
     * constructors are defined; both of which can add support for other
     * kinds of JSON values); if enabled, empty JSON String can be taken
     * to be equivalent of JSON null.
     *<p>
     * NOTE: this does NOT apply to scalar values such as booleans and numbers;
     * whether they can be coerced depends on
     * {@link MapperFeature#ALLOW_COERCION_OF_SCALARS}.
     *<p>
     * Feature is disabled by default.
     */

大致意思如下:

它允許將JSON中的空字串("")作為null值繫結到一個POJO或者Map或者Collection集合物件。如果禁用該配置項,那麼值為空字串的欄位在反序列化成一個POJO、Map、Collection時將會報錯。

⚠️注意!

不能理解成:他會把JSON字串中的“空字串”值在反序列化時轉為物件中對應欄位的null值。

具體來看下面例子:

public class Address {
    private String street;
    private String building;
    //省略getter/setter
}
public class User {
    private Integer id;
    private String name;
    private String address;
    private Address addressObj;
    private String[] hobbies;
    private Date birthDate;
    //省略getter/setter
}
        String userStr = "{\"id\":1,\"name\":\"徐健\",\"address\":\"\",\"addressObj\":\"\"}";
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,true);
        User user = objectMapper.readValue(userStr,User.class);
        System.out.println(objectMapper.writeValueAsString(user));

如果我們理解成他會把JSON字串中的“空字串”值在反序列化時轉為物件中對應欄位的null值,那麼我們期望反序列化之後address的值會從"“變為null,但是並沒有,在user物件中address的值依然是”"。

在這裡插入圖片描述

事實上作為Address物件的addressObj在反序列化之後從""變為了null。

在這裡插入圖片描述

因此我們應該正確理解

ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

這個配置項的註釋真正要表達的意思,那就是:
空字串""必須是要繫結到一個POJO、Map、Cpllection、陣列這樣的物件上(而不是普通的基本型別和String型別)時,才會在反序列化時轉為null。

對於上面的例子,我們如果禁用

ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

(將

objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,true);

註釋掉)會發現報錯如下:
在這裡插入圖片描述

DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

還是先看對應的JavaDoc:

/**
     * Feature that can be enabled to allow empty JSON Array
     * value (that is, <code>[ ]</code>) to be bound to POJOs (and
     * with 2.9, other values too) as `null`.
     * If disabled, standard POJOs can only be bound from JSON `null` or
     * JSON Object (standard meaning that no custom deserializers or
     * constructors are defined; both of which can add support for other
     * kinds of JSON values); if enabled, empty JSON Array will be taken
     * to be equivalent of JSON null.
     *<p>
     * Feature is disabled by default.
     * 
     * @since 2.5
     */

大致的意思是:

它允許將JSON中的空陣列([])作為null繫結到POJO等其他物件上。

事實真的是這樣嗎?

還是基於上面的例子稍微改動:

        String userStr = "{\"id\":1,\"name\":\"徐健\",\"address\":\"\",\"addressObj\":\"\",\"hobbies\":[]}";
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT,true);
        User user = objectMapper.readValue(userStr,User.class);
        System.out.println(objectMapper.writeValueAsString(user));

按照上面的JavaDoc,我們期望JSON字串裡面的hobbies欄位在反序列化以後變為null。

但是結果並沒有。它依舊被反序列化為了一個length=0的陣列

在這裡插入圖片描述

GitHub上有人丟擲了同樣的問題:ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT does not convert empty JSON array to null value

作者是這麼回答的:

Just to make clear: in Jackson documentation, "POJO" means roughly same as "Bean"; Java type that is handled as a set of property/value pairs. This does not include Collections, Maps or arrays (or scalar types). So while naming of ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT is too vague (it really should say POJO), it is not currently intended to coerce [ ] into null for container types.

But more generally I don't think I want to add coercions from JSON Array in cases where such input can be interpreted normally; that is, it is not intended to change empty List/array into Java null value.
I may need to update Javadocs to clarify this behavior.

大致意思如下:

需要說明的是:在Jackson檔案中,“ POJO”的含義與“ Bean”大致相同; 作為一組屬性/值對處理的Java型別。 這不包括Collections,Maps或陣列(或標量型別)。 因此,儘管對ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT的命名過於模糊(實際上應該說是POJO),但對於容器型別,當前不打算將[]強制為null。

但更一般而言,在可以正常解釋此類輸入的情況下,我不希望從JSON陣列新增強制。 也就是說,不打算將空的List / array更改為Java的null。
我可能需要更新Javadocs來闡明此行為。

簡單來說,這個配置項並不能把List/array更改為Java的null。

那問題來了,既然不能把list/array轉為null,那這個配置項有什麼用呢?

作者這麼回答的:

I thought use case was clear from document and my comment, but I think this is a bigger misunderstanding than I originally thought. What was intended and implemented is for "coercion" -- implicit conversion from incompatible type -- from (empty) JSON Array into non-array/non-collection type such as POJO or Map or even Scalar, as null.
This was to support some specific platform (PHP I think) that encodes nulls as [ ].
Without setting you would get basic "can not deserialize MyValue from JsonToken.START_ARRAY" exception.
New CoercionConfig (see #2113) will make this bit more clear as well as configurable.

As to converting empty Collection/array into null, which I think is what you want: only the reverse -- converting nulls into "empty", or skipping setting, or throwing Exception -- is possible currently. Functionality for doing that was added because avoiding nulls has been consistently brought up as a use case users want.
Inverse functionality -- making null out of "empty" value -- has been sometimes requested too, I think, but so far no support has been added.
I am always open to possibility of new functionality, features, but in this case it is not a matter of extending something that already is (mechanisms mentioned above are quite specific, partly since null value handling is quite separate from general JsonDeserializer deserialization flow for historical reasons...).

大致意思如下:

我認為用例從文件和評論中都很清楚,但是我認為這是一個比我最初想象的更大的誤解。意圖和實現的是“強制”(隱式地從不相容型別轉換)從(空)JSON陣列作為null到非陣列/非集合型別,例如POJO或Map甚至是Scalar。
這是為了支援某些特定的平臺(我認為PHP)將null編碼為[]。
如果不進行設定,您將獲得基本的“無法從JsonToken.START_ARRAY反序列化MyValue”異常。
新的CoercionConfig(請參閱#2113)將使這一點更加清晰和可配置。
至於將空的Collection / array轉換為null,我想這就是您想要的:目前只有相反的做法-將null轉換為“ empty”,跳過設定或引發Exception。這樣做的功能是增加的,因為避免空值一直是使用者希望用例提出的。
我認為有時也要求使用逆功能-將“空”值設為null-但到目前為止,尚未新增任何支援。
我總是對新功能和特性的可能性持開放態度,但是在這種情況下,擴充套件已存在的事物不是問題(上面提到的機制非常具體,部分原因是空值處理與針對歷史的一般JsonDeserializer反序列化流程完全不同原因…)。

簡而言之,這個選項是為了支援特定平臺(PHP等語言)會將null轉為[]的情況。如果不設定,在反序列化時會丟擲“無法從JsonToken.START_ARRAY反序列化MyValue”異常。

總結

  1. DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT是對POJO、Map、集合、陣列有效,而不是一個普通的string型別欄位。
  2. DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT在Java中不能達到預期的效果。

相關文章