我正在尝试实现一个XmlAdapter来修改某些对象属性的编组/解组。特别是,我尝试了这里描述的NullStringAdapter
:
Jaxb: xs:attribute null values
NullStringAdapter
的目标是将空值编组为空字符串,反之亦然。
与上述示例和我的代码的唯一区别在于,我想将适配器应用于元素,而不是属性,所以我拥有的是:
@XmlElement
@XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue; //someValue could be null, in that case the adapter should marshall it as an empty string
}
但是,经过一些调试后,我意识到在从Java到XML的编组过程中从未调用过Adapter方法!当XmlElement值为null时,会发生这种情况。 当此值不为null时,将按预期调用适配器方法。
感谢您的帮助!
答案 0 :(得分:5)
注意:我是EclipseLink JAXB (MOXy)潜在客户,也是JAXB 2 (JSR-222)专家组的成员。
然而,经过一些调试,我意识到Adapter方法是 在从Java到XML的编组过程中从未调用过!这发生了 当XmlElement值为null时。当此值不同于 null,将按预期调用适配器方法。
此行为因JAXB的实现而异。当字段/属性为null时,JAXB引用实现不会在XmlAdapter
上调用marshal方法,但MOXy将会。{/ p>
JAXB规范所说的内容(第5.5.1节简单属性)
get或is方法返回属性中指定的值 前一小节。如果返回null,则考虑该属性 不在它所代表的XML内容中。
MOXy对此陈述的解释是,字段/属性的值实际上是一旦经过XmlAdapter
后的值。这是支持behaviour that Sergio is looking for。
答案 1 :(得分:3)
当然,如果输入中没有任何元素可以触发该操作,则永远不会调用适配器。您链接的示例中发生的情况是显示具有空值的属性:
<element att="" />
这里的关键是是 att
属性,但它有一个空字符串。因此,JAXB unmarshaller会将其呈现给制定者。但是,因为它上面有一个适配器,它会通过那里并变成一个空值。
但如果你有这个
<element />
这是另一个故事。没有att
属性,因此永远不需要调用setter。
发生但没有内容但完全没有元素的元素之间存在差异。前者基本上可以被认为包含一个空字符串,但后者只是“不存在”。
编辑:使用这些类测试...
<强> Bean.java 强>
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name="Test")
public class Bean {
@XmlElement
@XmlJavaTypeAdapter(NullStringAdapter.class)
private String someValue;
public Bean() {
}
public String getSomeValue() {
return someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
<强> NullStringAdapter.java 强>
package jaxbadapter;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class NullStringAdapter extends XmlAdapter<String, String> {
@Override
public String unmarshal(final String v) throws Exception {
if("".equals(v)) {
return null;
}
return v;
}
@Override
public String marshal(final String v) throws Exception {
if(null == v) {
return "";
}
return v;
}
}
<强> ObjectFactory.java 强>
package jaxbadapter;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
@XmlRegistry
public class ObjectFactory {
public ObjectFactory() {
}
public Bean createBean() {
return new Bean();
}
@XmlElementDecl(name = "Test")
public JAXBElement<Bean> createTest(Bean value) {
return new JAXBElement<>(new QName("Test"), Bean.class, null, value);
}
}
<强> Main.java 强>
package jaxbadapter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;
public class Main {
public static void main(String[] args) throws Exception {
final JAXBContext context = JAXBContext.newInstance("jaxbadapter");
final Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
final ObjectFactory of = new ObjectFactory();
final Bean b1 = new Bean();
final Bean b2 = new Bean();
b2.setSomeValue(null);
final Bean b3 = new Bean();
b3.setSomeValue("");
m.marshal(of.createTest(b1), System.out);
System.out.println("");
m.marshal(of.createTest(b2), System.out);
System.out.println("");
m.marshal(of.createTest(b3), System.out);
System.out.println("");
}
}
这是输出:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test>
<someValue></someValue>
</Test>
其实让我感到很惊讶。然后我尝试将吸气剂更改为return someValue == null ? "" : someValue;
无效。然后在getter上设置一个断点,发现它永远不会被调用。
显然,JAXB使用反射来尝试和检索值,而不是在使用XmlAccessType.FIELD
时通过setter。性交。现在,您可以通过使用XmlAccessType.PROPERTY
代替并注释getter或setter来绕过此...
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {
private String someValue;
public Bean() {
}
@XmlElement
@XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
......但这仍然无济于事。在最后一个设置了空字符串的测试用例中,适配器的marshal
方法只被调用一次。显然它首先调用getter,当它返回null时,它只是简单地跳过适配器的东西。
我能想到的唯一解决方案就是在这里完全使用适配器并将替换放在getter中,确保使用XmlAccessType.PROPERTY
:
package jaxbadapter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {
private String someValue;
public Bean() {
}
@XmlElement
// @XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
return someValue == null ? "" : someValue;
}
public void setSomeValue(final String someValue) {
this.someValue = someValue;
}
}
这对我有用。如果您自己创建JAXB注释类并且不通过模式从XJC生成它们,那么它只是一个选项。
也许有人可以澄清一下为什么跳过适配器的空值以及是否有办法改变这种行为。