JAXB Marshal空字符串全局为空 [英] JAXB Marshal Empty String to Null Globally

查看:187
本文介绍了JAXB Marshal空字符串全局为空的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题非常类似于当字符串为空但不为空时,如何防止在JAXB中编组空标记

区别在于我无法添加package-info.java的注释,因为我们所有的JAXB类型都是从每次构建的模式生成的。
如果可能,我也更愿意不更改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类中的所有String字段?

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?

更新
我已设法获得通过进行以下更改,为模式中的所有字符串生成XML适配器:

Update I have managed to get the XML adapter generating for all Strings in the schema by making the following changes:

在项目POM中,我将其添加到maven-jaxb2-plugin:

In the project POM, I added this to the maven-jaxb2-plugin:

<bindingDirectory>src/main/resources</bindingDirectory>
<bindingIncludes>
    <include>bindings.xjb</include>
</bindingIncludes>

这是我的bindings.xjb文件:

And here is my bindings.xjb file:

<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>

格式化方法:

public static String printString(final String value)
{
    if (StringUtils.isBlank(value))
    {
        return null;
    }

    return value;
}

问题是这会导致JAXB内部出现空指针异常。这是stacktrace:

The problem is that this causes a Null Pointer Exception deep within JAXB. Here is the stacktrace:

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.

有没有办法覆盖在JAXB中使用的访问器,以便在确定是否呈现元素之前运行适配器?还有另一种方法可以实现我想要的吗?

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?

推荐答案

注意:我是 EclipseLink JAXB(MOXy) 领导和JAXB(JSR-222) 专家组。

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

您所做的是对的,您看到的错误是由于我认为 JAXB参考实施 。 JAXB RI应该能够处理从 XmlAdapter 返回的空值。这个用例适用于EclipseLink JAXB(MOXy),我将在下面举例说明。

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.

StringAdapter

下面是一个实现,它大致与您从XML模式生成Java模型后的内容大致相同(参见http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating.html )。

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).

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 类引用它下面的一个(参见: http://blog.bdoughan.com/2012/ 02 / jaxb-and-package-level-xmladapters.html )。

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).

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum11894193;

import javax.xml.bind.annotation.adapters.*;

Root

下面是一个示例域类,其中包含一些 String 字段。由于 XmlAdapter 是在包级别注册的,因此它将应用于该包中的所有映射的字符串字段/属性。

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.

package forum11894193;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    String a;
    String b;
    String c;

}

演示

在下面的演示代码中,我们将创建一个 Root的实例将几个字段设置为 然后将其封送到XML。

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.

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);
    }

}

使用JAXB RI输出

在此示例中使用JAXB RI会产生NPE。堆栈跟踪是不同的,但我们最有可能使用不同的编组方法。我还使用JDK中包含的JAXB RI版本,该版本重新打包到 com.sun.xml.internal.bind.v2

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.

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)

使用EclipseLink JAXB输出( MOXy)

当MOXy用作JAXB提供程序时,您将获得所需的输出。有关将MOXy指定为JAXB提供程序的信息,请参阅: http://blog.bdoughan .com / 2011/05 / specify-eclipselink-moxy-as-your.html

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.

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <b>b</b>
</root>

这篇关于JAXB Marshal空字符串全局为空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆