Java JAXB marshall/unmarshall 使用 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
由于 Java 类型像 'String' 只知道两种状态(null 或 not null),我尝试使用 Java 选项来解决这个问题。
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 进行编组和解组。这个想法是,只有当需要将值显式设置为值时,才会调用字段的 setter 。这意味着隐式缺少值。
我查看了其他类似以下的 Whosebug 问题,但所有这些问题都无法完整处理我需要实现的行为:
How to generate JaxB-Classes with java.util.Optional?
Using generic @XmlJavaTypeAdapter to unmarshal wrapped in Guava's Optional
Using Guava's Optional with @XmlAttribute
这两天我一直在为这个问题苦苦挣扎。我尝试使用 XMLAdapter 和 GenericAdapter,尝试了几种方法来注释字段和 getter/setter 与 @XmlElement,尝试使用 @XmlAnyElment 有无松懈,但所有这些都只导致了部分成功。要么是 nil 值没有被正确处理,要么是列表没有被正确打印出来,...
我认为每个 Java 正确实施补丁操作的网络服务都应该有这个问题。 (不是在谈论“json 补丁方法”(RFC 6902))
有解决我问题的通用方法吗?
您可以在 java class 中使用一些验证,例如 @NotNull、@Size 等。或者您可以输入默认值,以确保它不会为空。之后,您可以使用推荐的 Xml 注释创建 DTO(数据传输对象)并使用 ModelMapper 对其进行映射。
以下代码能够区分空名称和空名称。为了使解决方案有效,我创建了一个 PersonList 元素来包含所有的 person 元素。如果元素被 XML:
显式设置为 null,则每个 Person 都包含一个 Name,该 Name 的 isNil() return 为真
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();
}
}
主要class演示解决方案:
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(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 生成相应的 (XML)Java 类和 XSD 文件。生成的 类 中的所有字段都是 JAXBElement<> 类型,以便能够保持所需的 3 种状态(不存在、空、someValue)。
在业务模型方面,我使用 Java 类 和 Optional<> 字段类型来保持 3 种状态。
对于映射,我编写了一个映射器,它使用反射递归地从 JAXB 映射到 Java,反之亦然。当从 Java 映射到 JAXB 时,映射器使用 ObjectFactory 来创建 JAXBElement 对象。 (Mapper 本身只有大约 300 行代码)。
字段是根据匹配的字段名称映射的。
最丑陋和最具挑战性的部分是,需要更改 XSD 模式文件,以便使 JAXB 生成 类 使用 JAXBElement 字段类型。因此,如果尚未设置,我必须手动将 minOccurs="0" nillable="true" 属性添加到 XML 元素。
通过上述解决方案,考虑到所需的 3 个状态,我最终成功地将 XML 映射到 Java,反之亦然。
当然,这个解决方案有其缺点。
一种是手动修改XSD文件。通常不好的做法是更改外部系统提供的 XSD 文件,该文件充当接口合同。
对于我当时的要求,该解决方案非常有效。甚至对外部系统的接口契约的修改也可以很容易地实现。
我的应用程序需要在 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 类型像 'String' 只知道两种状态(null 或 not null),我尝试使用 Java 选项来解决这个问题。
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 进行编组和解组。这个想法是,只有当需要将值显式设置为值时,才会调用字段的 setter 。这意味着隐式缺少值。
我查看了其他类似以下的 Whosebug 问题,但所有这些问题都无法完整处理我需要实现的行为:
How to generate JaxB-Classes with java.util.Optional?
Using generic @XmlJavaTypeAdapter to unmarshal wrapped in Guava's Optional
Using Guava's Optional with @XmlAttribute
这两天我一直在为这个问题苦苦挣扎。我尝试使用 XMLAdapter 和 GenericAdapter,尝试了几种方法来注释字段和 getter/setter 与 @XmlElement,尝试使用 @XmlAnyElment 有无松懈,但所有这些都只导致了部分成功。要么是 nil 值没有被正确处理,要么是列表没有被正确打印出来,...
我认为每个 Java 正确实施补丁操作的网络服务都应该有这个问题。 (不是在谈论“json 补丁方法”(RFC 6902))
有解决我问题的通用方法吗?
您可以在 java class 中使用一些验证,例如 @NotNull、@Size 等。或者您可以输入默认值,以确保它不会为空。之后,您可以使用推荐的 Xml 注释创建 DTO(数据传输对象)并使用 ModelMapper 对其进行映射。
以下代码能够区分空名称和空名称。为了使解决方案有效,我创建了一个 PersonList 元素来包含所有的 person 元素。如果元素被 XML:
显式设置为 null,则每个 Person 都包含一个 Name,该 Name 的 isNil() return 为真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();
}
}
主要class演示解决方案:
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(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 生成相应的 (XML)Java 类和 XSD 文件。生成的 类 中的所有字段都是 JAXBElement<> 类型,以便能够保持所需的 3 种状态(不存在、空、someValue)。
在业务模型方面,我使用 Java 类 和 Optional<> 字段类型来保持 3 种状态。
对于映射,我编写了一个映射器,它使用反射递归地从 JAXB 映射到 Java,反之亦然。当从 Java 映射到 JAXB 时,映射器使用 ObjectFactory 来创建 JAXBElement 对象。 (Mapper 本身只有大约 300 行代码)。 字段是根据匹配的字段名称映射的。
最丑陋和最具挑战性的部分是,需要更改 XSD 模式文件,以便使 JAXB 生成 类 使用 JAXBElement 字段类型。因此,如果尚未设置,我必须手动将 minOccurs="0" nillable="true" 属性添加到 XML 元素。
通过上述解决方案,考虑到所需的 3 个状态,我最终成功地将 XML 映射到 Java,反之亦然。
当然,这个解决方案有其缺点。 一种是手动修改XSD文件。通常不好的做法是更改外部系统提供的 XSD 文件,该文件充当接口合同。
对于我当时的要求,该解决方案非常有效。甚至对外部系统的接口契约的修改也可以很容易地实现。