My question is very similar to How to prevent marshalling empty tags in JAXB when string is empty but not null
我的问题非常类似于当字符串为空但不为空时,如何防止JAXB中编组空标记。
The difference is that I am unable to add the annotation to package-info.java as all our JAXB types are generated from schemas with every build. I also would much prefer not to change JAXB providers if possible.
不同之处在于,我无法将注释添加到包信息中。我们所有的JAXB类型都是由每个构建的模式生成的。如果可能的话,我也不希望更改JAXB提供者。
What I want to achieve is that setting an empty String will not create the element, but I need to set this for all generated JAXB types from many schemas. Is there a way to apply this to all String fields in all generated JAXB classes?
我想要实现的是,设置一个空字符串不会创建元素,但是我需要从许多模式中为所有生成的JAXB类型设置这个元素。是否有方法将其应用到所有生成的JAXB类中的所有字符串字段?
Update I have managed to get the XML adapter generating for all Strings in the schema by making the following changes:
通过进行以下更改,我已经成功地为模式中的所有字符串生成了XML适配器。
In the project POM, I added this to the maven-jaxb2-plugin:
在项目POM中,我将它添加到maven-jaxb2-plugin:
<bindingDirectory>src/main/resources</bindingDirectory>
<bindingIncludes>
<include>bindings.xjb</include>
</bindingIncludes>
And here is my bindings.xjb file:
这是我的绑定。xjb文件:
<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1">
<jxb:globalBindings>
<jxb:javaType name="java.lang.String" xmlType="xs:token"
parseMethod="com.project.Formatter.parseString"
printMethod="com.project.Formatter.printString"/>
</jxb:globalBindings>
</jxb:bindings>
And the formatting method:
和格式的方法:
public static String printString(final String value)
{
if (StringUtils.isBlank(value))
{
return null;
}
return value;
}
The problem is that this causes a Null Pointer Exception deep within JAXB. Here is the stacktrace:
问题是,这会在JAXB内部导致一个空指针异常。这是加亮:
Caused by: java.lang.NullPointerException
at com.sun.xml.bind.v2.runtime.output.SAXOutput.text(SAXOutput.java:158)
at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:321)
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:210)
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:209)
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:250)
at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:98)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:185)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:305)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:312)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:71)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:257)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103)
The cause of this problem boils down to this method:
这个问题的起因可归结为这个方法:
com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.CompositeTransducedAccessorImpl.hasValue(BeanT)
The above method will render the element if the value is not null before any adapter is run.
在运行任何适配器之前,如果值不为空,则上述方法将呈现该元素。
Is there any way to override the Accessor used in JAXB so that this will run the adapter before determining whether to render the element? Is there another way to achieve what I want?
是否有办法覆盖JAXB中使用的访问器,以便在决定是否呈现该元素之前运行适配器?有没有另一种方法来实现我想要的?
3 个解决方案
#1
13
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
注意:我是EclipseLink JAXB (MOXy)领导和JAXB (JSR-222)专家组成员。
What you have done is right, the error you are seeing is due to what I believe is a bug in the JAXB reference implementation. The JAXB RI should be able to handle a null value being returned from an XmlAdapter
. This use case works with EclipseLink JAXB (MOXy), I'll demonstrate below with an example.
您所做的是正确的,您所看到的错误是由于我所认为的JAXB参考实现中的错误。JAXB RI应该能够处理从XmlAdapter返回的空值。这个用例与EclipseLink JAXB (MOXy)一起工作,下面我将以一个示例来演示。
StringAdapter
StringAdapter
Below is an implmentation that does approximately what the one that you will get after you generate your Java model from the XML schema (see http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html).
下面是在从XML模式生成Java模型后,您将得到的一种请求(请参阅http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html)。
package forum11894193;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
@Override
public String marshal(String string) throws Exception {
if("".equals(string)) {
return null;
}
return string;
}
@Override
public String unmarshal(String string) throws Exception {
return string;
}
}
package-info
package-info
Since you are registering a global adapter, it will be referenced from a package-info
class like the one below (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
由于您正在注册一个全局适配器,所以它将从以下的package-info类中引用(参见:http://blog.bdoughan.com/2012/02/jaxb/packagelevel-xmladapters.html)。
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum11894193;
import javax.xml.bind.annotation.adapters.*;
Root
根
Below is a sample domain class with a few String
fields. Since the XmlAdapter
was registered at the package level it will apply to all mapped String fields/properties in that package.
下面是一个带有几个字符串字段的示例域类。由于XmlAdapter是在包级别注册的,所以它将应用于该包中的所有映射的字符串字段/属性。
package forum11894193;
import javax.xml.bind.annotation.*;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
String a;
String b;
String c;
}
Demo
演示
In the demo code below we'll create an instance of Root
set a couple of the fields to ""
and then marshal it to XML.
在下面的演示代码中,我们将创建一个根集的实例,将几个字段设置为“”,然后将其封为XML。
package forum11894193;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.a = "";
root.b = "b";
root.c = "";
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output Using JAXB RI
输出使用JAXB国际扶轮
Using the JAXB RI with this example results in a NPE. The stack trace is different, but most likely to us using different marshal methods. I am also using the version of the JAXB RI included in the JDK which is repackaged to com.sun.xml.internal.bind.v2
.
使用JAXB RI与这个示例的结果是NPE。堆栈跟踪是不同的,但最有可能使用不同的marshal方法。我还使用了在JDK中包含的JAXB RI的版本,它被重新打包到com.sun.xml.intern.bind.v2。
Exception in thread "main" java.lang.NullPointerException
at com.sun.xml.internal.bind.v2.runtime.output.Encoded.setEscape(Encoded.java:96)
at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.doText(UTF8XmlOutput.java:294)
at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:283)
at com.sun.xml.internal.bind.v2.runtime.output.IndentingUTF8XmlOutput.text(IndentingUTF8XmlOutput.java:141)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:293)
at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179)
at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166)
at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239)
at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
at forum11894193.Demo.main(Demo.java:17)
Output Using EclipseLink JAXB (MOXy)
使用EclipseLink JAXB (MOXy)输出
When MOXy is used as the JAXB provider you get the desired output. For information on specifying MOXy as your JAXB provider see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html.
当使用MOXy作为JAXB提供程序时,您得到所需的输出。有关指定MOXy的信息,请参见:http://blog.bdoughan.com/2011/05/specifying-eclipselink- MOXy .html。
<?xml version="1.0" encoding="UTF-8"?>
<root>
<b>b</b>
</root>
#2
0
An empty string is still a value. That's why the element is created. With that said, what about in your setter methods set the variable to null if the string is empty?
空字符串仍然是一个值。这就是创建元素的原因。这样的话,如果字符串是空的,那么在setter方法中如何将变量设置为null ?
Also, check this thread out JAXB: how to make JAXB NOT to unmarshal empty string to 0
另外,检查这个线程的JAXB:如何使JAXB不将空字符串解封为0。
#3
0
Use Below code into you Marshall class
使用下面的代码进入您的Marshall类。
public static String toXml(Object o, Class clazz, boolean isFormatted, boolean isEmptyNodes) {
try {
Map<String, Object> properties = new HashMap<String, Object>(1);
if(isEmptyNodes) {
SessionEventListener sessionEventListener = new NullPolicySessionEventListener();
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
} else {
SessionEventListener sessionEventListener = new DiscardEmptyTagSessionEventListener();
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
}
// Create a JaxBContext
JAXBContext jc = JAXBContext.newInstance(new Class[] {clazz}, properties);
StringWriter sw = new StringWriter();
// Create the UnMarshaller Object using the JaxB Context
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormatted);
// remove the xml version line from the output
marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
// Marshal the employee object to XML and print the output to console
marshaller.marshal(o, sw);
return sw.toString();
} catch (JAXBException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
#1
13
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
注意:我是EclipseLink JAXB (MOXy)领导和JAXB (JSR-222)专家组成员。
What you have done is right, the error you are seeing is due to what I believe is a bug in the JAXB reference implementation. The JAXB RI should be able to handle a null value being returned from an XmlAdapter
. This use case works with EclipseLink JAXB (MOXy), I'll demonstrate below with an example.
您所做的是正确的,您所看到的错误是由于我所认为的JAXB参考实现中的错误。JAXB RI应该能够处理从XmlAdapter返回的空值。这个用例与EclipseLink JAXB (MOXy)一起工作,下面我将以一个示例来演示。
StringAdapter
StringAdapter
Below is an implmentation that does approximately what the one that you will get after you generate your Java model from the XML schema (see http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html).
下面是在从XML模式生成Java模型后,您将得到的一种请求(请参阅http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html)。
package forum11894193;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class StringAdapter extends XmlAdapter<String, String> {
@Override
public String marshal(String string) throws Exception {
if("".equals(string)) {
return null;
}
return string;
}
@Override
public String unmarshal(String string) throws Exception {
return string;
}
}
package-info
package-info
Since you are registering a global adapter, it will be referenced from a package-info
class like the one below (see: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html).
由于您正在注册一个全局适配器,所以它将从以下的package-info类中引用(参见:http://blog.bdoughan.com/2012/02/jaxb/packagelevel-xmladapters.html)。
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum11894193;
import javax.xml.bind.annotation.adapters.*;
Root
根
Below is a sample domain class with a few String
fields. Since the XmlAdapter
was registered at the package level it will apply to all mapped String fields/properties in that package.
下面是一个带有几个字符串字段的示例域类。由于XmlAdapter是在包级别注册的,所以它将应用于该包中的所有映射的字符串字段/属性。
package forum11894193;
import javax.xml.bind.annotation.*;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
String a;
String b;
String c;
}
Demo
演示
In the demo code below we'll create an instance of Root
set a couple of the fields to ""
and then marshal it to XML.
在下面的演示代码中,我们将创建一个根集的实例,将几个字段设置为“”,然后将其封为XML。
package forum11894193;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class);
Root root = new Root();
root.a = "";
root.b = "b";
root.c = "";
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
Output Using JAXB RI
输出使用JAXB国际扶轮
Using the JAXB RI with this example results in a NPE. The stack trace is different, but most likely to us using different marshal methods. I am also using the version of the JAXB RI included in the JDK which is repackaged to com.sun.xml.internal.bind.v2
.
使用JAXB RI与这个示例的结果是NPE。堆栈跟踪是不同的,但最有可能使用不同的marshal方法。我还使用了在JDK中包含的JAXB RI的版本,它被重新打包到com.sun.xml.intern.bind.v2。
Exception in thread "main" java.lang.NullPointerException
at com.sun.xml.internal.bind.v2.runtime.output.Encoded.setEscape(Encoded.java:96)
at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.doText(UTF8XmlOutput.java:294)
at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:283)
at com.sun.xml.internal.bind.v2.runtime.output.IndentingUTF8XmlOutput.text(IndentingUTF8XmlOutput.java:141)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:293)
at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179)
at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166)
at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239)
at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
at forum11894193.Demo.main(Demo.java:17)
Output Using EclipseLink JAXB (MOXy)
使用EclipseLink JAXB (MOXy)输出
When MOXy is used as the JAXB provider you get the desired output. For information on specifying MOXy as your JAXB provider see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html.
当使用MOXy作为JAXB提供程序时,您得到所需的输出。有关指定MOXy的信息,请参见:http://blog.bdoughan.com/2011/05/specifying-eclipselink- MOXy .html。
<?xml version="1.0" encoding="UTF-8"?>
<root>
<b>b</b>
</root>
#2
0
An empty string is still a value. That's why the element is created. With that said, what about in your setter methods set the variable to null if the string is empty?
空字符串仍然是一个值。这就是创建元素的原因。这样的话,如果字符串是空的,那么在setter方法中如何将变量设置为null ?
Also, check this thread out JAXB: how to make JAXB NOT to unmarshal empty string to 0
另外,检查这个线程的JAXB:如何使JAXB不将空字符串解封为0。
#3
0
Use Below code into you Marshall class
使用下面的代码进入您的Marshall类。
public static String toXml(Object o, Class clazz, boolean isFormatted, boolean isEmptyNodes) {
try {
Map<String, Object> properties = new HashMap<String, Object>(1);
if(isEmptyNodes) {
SessionEventListener sessionEventListener = new NullPolicySessionEventListener();
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
} else {
SessionEventListener sessionEventListener = new DiscardEmptyTagSessionEventListener();
properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
}
// Create a JaxBContext
JAXBContext jc = JAXBContext.newInstance(new Class[] {clazz}, properties);
StringWriter sw = new StringWriter();
// Create the UnMarshaller Object using the JaxB Context
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormatted);
// remove the xml version line from the output
marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
// Marshal the employee object to XML and print the output to console
marshaller.marshal(o, sw);
return sw.toString();
} catch (JAXBException e) {
throw new RuntimeException(e.getMessage(), e);
}
}