jackson-dataformat-xmlを2.11.3から2.12.7にアップデートしたところ、XMLの空エレメントに対するデシリアライズの挙動に違いがあった。

概要

2.12のリリースで、XMLのハンドリングが各種改良され、空エレメントに対してのデシリアライズ処理も改良された。

Jackson Release 2.12

Jackson 2.12: improved XML module

Change default setting of FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL from true to false

XML module

Default for FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL changed from true (2.9 - 2.11) to false,

so that no automatic coercion done from empty elements like into null

EMPTY_ELEMENT_AS_NULL機能が恒久的に無効となったため、空エレメントがnullにマッピングされなくなった。

which is usually more logical setting for most users.
You can still enable FromXmlParser.EMPTY_ELEMENT_AS_NULLto keep earlier logic if you want,
but the default was changed and handling should in general be more robust.

説明には、ほとんどのユーザにとって一般的な設定となり、有効にすることも可能だが、無効のほうが堅牢性が良いと記載されている。

環境

  • Java15
  • jackson-dataformat-xml 2.12.7

EMPTY_ELEMENT_AS_NULLを有効にして利用する

有効にさせることで堅牢性が損なわれるといったニュアンスの説明があったが、現状扱っているXMLの構造的に、空エレメントは空文字では無く未存在(NULL)を表現するのに適切であるので、以前の挙動と同様となるようNULLにデシリアライズさせる。

Student.javaimport com.fasterxml.jackson.annotation.JsonProperty;

public class Student {

    @JsonProperty("name")
    private String name;

    @JsonProperty("age")
    private int age;

    @JsonProperty("address")
    private Address address;

    public String toString() {
        return "Student(name=" + this.name + ", age=" + this.age + ", address=" + this.address + ")";
    }

    static class Address {
        @JsonProperty("prefecture")
        private String prefecture;

        @JsonProperty("city")
        private String city;

        public String toString() {
            return "Student.Address(prefecture=" + this.prefecture + ", city=" + this.city + ")";
        }
    }
}
Main.javaimport com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import jackson_accept_empty_string_as_null_object.Student;

import static com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser.Feature.EMPTY_ELEMENT_AS_NULL;

public class Main {

    public static void main(String[] args) throws JsonProcessingException {
        XmlMapper xmlMapper = new XmlMapper();
        xmlMapper.configure(EMPTY_ELEMENT_AS_NULL, true); // 明示的に機能を有効にする。

        String xmlString = """
                <root>
                    <name>taro</name>
                    <age>30</age>
                    <address>
                        <prefecture>東京都</prefecture>
                        <city>江東区</city>
                    </address>
                </root>
                """;
        System.out.println(xmlMapper.readValue(xmlString, Student.class));

        String xmlString2 = """
                <root>
                    <name></name>
                    <age></age>
                    <address></address>
                </root>
                """;
        System.out.println(xmlMapper.readValue(xmlString2, Student.class));

        String xmlString3 = """
                <root>
                    <name />
                    <age />
                    <address />
                </root>
                """;
        System.out.println(xmlMapper.readValue(xmlString3, Student.class));
    }
}
実行結果Student(name=taro, age=30, address=Student.Address(prefecture=東京都, city=江東区))
Student(name=, age=0, address=Student.Address(prefecture=null, city=null))
Student(name=null, age=0, address=null)

住所自体の設定が無いケース(xmlString3)を想定した場合、Address自体のインスタンスがnullで初期化されるため、
null判定などオブジェクトの取り扱いがし易いと感じた。