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

由于像"字符串"这样的Java类型只知道两种状态(null或非null(,我尝试使用Java Optionals来解决这个问题。

XML 和 Java Optionals 之间的映射可能如下所示:

<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 进行编组和解组。这个想法是,仅当需要将值显式设置为值时,才会调用字段的 setter。这意味着值隐式缺失。


我查看了其他堆栈溢出问题,如下所示,但所有这些问题都不完整地处理了我需要实现的行为:

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

使用通用@XmlJavaTypeAdapter解组包裹在番石榴的可选

将番石榴的可选与@XmlAttribute

一起使用我已经为这个问题苦苦挣扎了两天了。我尝试使用XMLAdapter和GenericAdapter,尝试了几种方法如何用@XmlElement注释字段和getter/setter,尝试使用有和没有松弛的@XmlAnyElment,但所有这些都只导致了部分成功。要么 nil 值未正确处理,要么列表未正确打印出来,...

我认为每个具有正确实现补丁操作的 Java Web 服务都应该有这个问题。(不是在谈论"JSON 补丁方法"(RFC 6902((

有没有解决我问题的常用方法?

下面的代码能够区分空名称与空名称。 为了使解决方案正常工作,我创建了一个 PersonList 元素来包含所有 person 元素。 每个人都包含一个名称,如果 XML 明确将元素设置为 null,则该名称将具有 isNil(( 返回 true:

人.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())));
}
}

姓名.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()+"""));
}
}

人员列表.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(people = [Person(optionalName.isPresent(( = true, name = Name(nil = false, value = "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(。

在商业模式方面,我使用了具有可选<>字段类型的 Java 类来保存 3 种状态。

对于映射,我编写了一个映射器,它使用反射从JAXB递归映射到Java,反之亦然。当从 Java 映射到 JAXB 时,映射器使用 ObjectFactory 来创建 JAXBElement 对象。(映射器本身只有大约 300 行代码(。 字段是根据匹配的字段名称映射的。

最丑陋和最具挑战性的部分是,需要更改XSD模式文件,以便使JAXB生成的类使用JAXBElement字段类型。因此,我必须手动将属性minHappen="0" nillable="true">添加到XML元素中(如果尚未设置(。

通过上面的解决方案,我终于设法将 XML 映射到 Java,反之亦然,考虑到所需的 3 种状态,很容易。

当然,这种解决方案有其缺点。 一种是手动修改XSD文件。更改外部系统提供的 XSD 文件通常是不好的做法,它充当接口协定。

对于我当时的要求,该解决方案非常有效。甚至对外部系统的接口协定的更改也可以非常容易地实现。

你可以在java类中使用一些验证,如@NotNull、@Size等。或者你可以输入默认值,以确保它不会为空。之后,您可以使用推荐的 Xml 注释创建 DTO(数据传输对象(,并使用模型映射器对其进行映射。

最新更新