簡體   English   中英

Java JAXB 編組/解組使用 Java 可選

[英]Java JAXB marshall/unmarshall using Java Optionals

我的應用程序需要在 Java 和 XML 之間轉換數據。

轉換數據時,我需要區分該值是否存在,該值是否明確設置為 null 或該值是否有值。

XML 示例:

<person><name>Bob</name></person>     <-- element 'name' contains value "Bob"

<person><name nil="true"/></person>   <-- element 'name' was set explicitly to 'nil'/null

<person></person>                     <-- element 'name' is missing

由於像“String”這樣的 Java 類型只知道兩種狀態(null 或 not null),我嘗試使用 Java Optionals 來解決這個問題。

XML 和 Java 可選值之間的映射可能如下所示:

<person></person>                   <=> Optional<String> name = null;

<person><name>Bob</name></person>   <=> Optional<String> name = Optional.of("Bob");

<person><name nil="true"/></person> <=> Optional<String> name = Optional.empty();

我嘗試使用 JAXB 進行編組和解組。 這個想法是,只有當需要將值顯式設置為值時,才會調用字段的設置器。 這意味着隱含地缺少一個值。


我查看了其他類似以下的 stackoverflow 問題,但所有這些問題在處理我需要實現的行為時都不完整:

如何使用 java.util.Optional 生成 JaxB 類?

使用通用@XmlJavaTypeAdapter 解組包裝在 Guava 的 Optional 中

將 Guava 的 Optional 與 @XmlAttribute 一起使用

我已經為這個問題苦苦掙扎了兩天了。 我嘗試使用 XMLAdapter 和 GenericAdapter,嘗試了幾種方法如何使用 @XmlElement 注釋字段和 getter/setter,嘗試使用 @XmlAnyElment 有無松懈,但所有這些都只取得了部分成功。 要么是 nil 值沒有被正確處理,要么是列表沒有被正確打印出來,...

我認為每個 Java 的 webservice 正確實施補丁操作都應該有這個問題。 (不是在談論“json 補丁方法”( RFC 6902 ))

有解決我的問題的通用方法嗎?

以下代碼能夠區分空名和空名。 為了使解決方案有效,我創建了一個PersonList元素來包含所有person元素。 每個Person包含一個Name,如果XML將該元素顯式設置為null,則它將具有isNil()返回true:

Person.java:

import java.util.Optional;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(propOrder = {"name"})
@XmlRootElement(name = "person")
public class Person {

    private Optional<Name> optionalName;

    public Person() {
        optionalName = Optional.<Name>empty();
    }

    public Optional<Name> getOptionalName() {
        return optionalName;
    }

    public Name getName() {
        return (optionalName.isPresent()) ? (optionalName.get()) : (null);
    }

    @XmlElement(name = "name", required = false)
    public void setName(Name name) {
        optionalName = Optional.ofNullable(name);
    }

    @Override
    public String toString() {
        return String.format("Person(optionalName.isPresent() = %s, name = %s)",
                             Boolean.toString(optionalName.isPresent()),
                             ((getName() == null) ? ("null") : (getName().toString())));
    }
}

Name.java:

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "name")
public class Name {

    @XmlAttribute(name = "nil")
    private boolean nil;

    @XmlValue
    private String value;

    public Name() {
        nil = false;
        value = null;
    }

    public boolean isNil() {
        return nil;
    }

    public void setNil(boolean torf) {
        this.nil = torf;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return String.format("Name(nil = %s, value = %s)",
                             Boolean.toString(nil),
                             (value == null) ? ("null"):("\""+getValue()+"\""));
    }
}

PersonList.java:

import java.util.Iterator;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "PersonList")
public class PersonList {

    private List<Person> persons;

    public PersonList() {
        persons = null;
    }

    @XmlElement(name = "person")
    public List<Person> getPersons() {
        return persons;
    }

    public void setPersons(List<Person> persons) {
        this.persons = persons;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("PersonList(persons = ");
        if(persons == null) {
            sb.append("null");
        }
        else {
            sb.append("[");
            Iterator<Person> iterator = persons.iterator();
            while(iterator.hasNext()) {
                sb.append(iterator.next().toString());
                if(iterator.hasNext()) {
                    sb.append(", ");
                }
            }
            sb.append("]");
        }
        sb.append(")");
        return sb.toString();
    }
}

主類演示解決方案:

import java.io.ByteArrayInputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

public class XmlOptional {
    public static final int STATUS_OKAY = 0;
    public static final int STATUS_ERROR = -1;

    public static final String XML_DATA = "<PersonList>" +
                                          "<person><name>Bob</name></person>" +
                                          "<person><name nil=\"true\" /></person>" +
                                          "<person></person>" +
                                          "</PersonList>";

    private XmlOptional() {
    }

    private static PersonList loadXml() {
        try {
            ByteArrayInputStream bais = new ByteArrayInputStream(XML_DATA.getBytes());
            JAXBContext context = JAXBContext.newInstance(PersonList.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            PersonList personList = (PersonList)unmarshaller.unmarshal(bais);
            return personList;
        }
        catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        int status = STATUS_OKAY;

        try {
            PersonList personList = loadXml();
            System.out.format("Xml contained: %s%n", personList);
        }
        catch (Throwable thrown) {
            status = STATUS_ERROR;
            thrown.printStackTrace();
        }
        finally {
            System.exit(status);
        }
    }
}

樣本輸出:

包含的Xml:PersonList(persons = [Person(Person(optionalName.isPresent()= true,name = Name(nil = false,值=“ Bob”))),Person(optionalName.isPresent()= true,name = Name(nil = true,value =“”)),Person(optionalName.isPresent()= false,name = null)])

由於僅靠正確使用和配置JAXB無法完全解決問題,我決定按如下方式解決:

(主要目標是寫一個子系統來與基於XML的外部系統通信)

作為起點,我使用目標系統提供的 XSD 模式與 JAXB 和 XSD 文件進行通信並生成相應的 (XML)Java 類。 這些生成的類中的所有字段都是 JAXBElement<> 類型,以便能夠保持所需的 3 種狀態(不存在、null、someValue)。

在業務 model 方面,我使用了具有 Optional<> 字段類型的 Java 類來保持 3 種狀態。

對於映射,我寫了一個映射器,它使用反射從 JAXB 遞歸到 map 到 Java,反之亦然。 當從 Java 映射到 JAXB 時,映射器使用 ObjectFactory 創建 JAXBElement 對象。 (Mapper 本身只有大約 300 行代碼)。 這些字段是根據匹配的字段名稱映射的。

最丑陋和最具挑戰性的部分是,需要更改 XSD 模式文件,以便生成使用 JAXBElement 字段類型的 JAXB 生成的類。 因此,我必須手動將屬性minOccurs="0" nillable="true"添加到 XML 元素(如果尚未設置)。

有了上面的解決方案,考慮到所需的 3 個狀態,我最終成功地從 map 到 XML 到 Java,反之亦然。

當然,這種解決方案有其缺點。 一種是手動修改XSD文件。 通常不好的做法是更改外部系統提供的 XSD 文件,該文件充當接口合同。

對於我當時的要求,該解決方案非常有效。 即使對外部系統的接口契約進行更改也可以非常容易地實現。

您可以在Java類中使用一些驗證,例如@ NotNull,@ Size等。 或者,您可以將默認值(可以確定)設置為非null。 之后,您可以使用推薦的Xml注釋創建DTO(數據傳輸對象),並使用ModelMapper對其進行映射。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM