[英]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.