java JAXB 将空字符串全局设置为空

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11894193/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 06:48:30  来源:igfitidea点击:

JAXB Marshal Empty String to Null Globally

javajaxb

提问by J Barclay

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.

不同之处在于我无法将注释添加到 package-info.java,因为我们所有的 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 类中的所有 String 字段?

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

这是我的 bindings.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.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:210)
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl.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.serializeBody(ElementBeanInfoImpl.java:156)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.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 beforeany 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 中使用的访问器,以便在确定是否呈现元素之前运行适配器?有没有另一种方法来实现我想要的?

回答by bdoughan

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

字符串适配器

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

包信息

Since you are registering a global adapter, it will be referenced from a package-infoclass 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-and-package-level-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 Stringfields. Since the XmlAdapterwas registered at the package level it will apply to all mapped String fields/properties in that package.

下面是一个包含几个String字段的示例域类。由于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 Rootset a couple of the fields to ""and then marshal it to XML.

在下面的演示代码中,我们将创建一个实例,Root将几个字段设置为"",然后将其编组为 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 RI 的输出

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.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.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179)
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl.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 指定为 JAXB 提供程序的信息,请参阅:http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html 。

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

回答by km1

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: how to make JAXB NOT to unmarshal empty string to 0这个线程

回答by Abhishek

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