如何使用JAXB将null值表示为空元素?

11

我的XSD结构如下:

<element name="XYZDate" maxOccurs="1" minOccurs="1" nillable="true" type="date"/>
当我将此字段设置为null时,它可以通过,但在使用JAXB编组生成XML时,它会输出:
<XYZDate xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>

虽然我想要输出结果为<XYZDate/>,即不需要架构和其他属性。
我使用XMLStreamWriter来消除它,但它会将完整的XML生成在一行中。我需要格式良好且格式正确的XML

如果我需要使用IndentingXMLStreamWriter,我的Java版本不支持它,并且我无法控制Java容器以进行更改或修改。

请建议任何解决方案以形成格式良好的XML


1
https://dev59.com/-2445IYBdhLWcg3w9Oss - Jean Logeart
正如我所写的,我无法使用IndentingXMLStreamWriter,因为我正在使用Eclipse,并且要使用这个类,我需要在创建问题的Lib中添加JAXB-API 2.1.9,而Java 1.6则使用它自己的库。 - Amit Telang
我更改了你的问题标题,因为JAXB根据你的元数据生成了正确的输出(请参见http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html)。你真正想问的是如何将null表示为空元素。 - bdoughan
4个回答

16

注意 #1:我是EclipseLink JAXB(MOXy)的负责人,也是JAXB(JSR-222)专家组成员。


注意 #2:您所看到的输出与您在JAXB中映射的内容相匹配。有关更多信息,请参见:


将NULL表示为空元素

如果要将null表示为空元素,则有几个选项。

选项#1-使用标准的JAXB API

DateAdapter

您可以使用XmlAdapter来更改将Date实例编排为XML时的方式。我们将把日期转换为一个具有一个使用@XmlValue映射的属性的类的实例(请参见http://blog.bdoughan.com/2011/06/jaxb-and-complex-types-with-simple.html)。JAXB RI不会为null值调用XmlAdapter 机制,因此您需要使用支持此类机制的JAXB实现,例如MOXy。

package forum11743306;

import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.datatype.XMLGregorianCalendar;

public class DateAdapter extends XmlAdapter<DateAdapter.AdaptedDate, XMLGregorianCalendar>{

    @Override
    public AdaptedDate marshal(XMLGregorianCalendar date) throws Exception {
        AdaptedDate adaptedDate = new AdaptedDate();
        adaptedDate.value = date;
        return adaptedDate;
    }

    @Override
    public XMLGregorianCalendar unmarshal(AdaptedDate adaptedDate) throws Exception {
        return adaptedDate.value;
    }

    public static class AdaptedDate {
        @XmlValue
        public XMLGregorianCalendar value;
    }

}

使用@XmlJavaTypeAdapter注释来引用XmlAdapter

package forum11743306;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.datatype.XMLGregorianCalendar;

@XmlRootElement
public class Root {

    private XMLGregorianCalendar xyzDate;

    @XmlElement(name = "XYZDate", required=true, nillable = true)
    @XmlJavaTypeAdapter(DateAdapter.class)
    public XMLGregorianCalendar getXyzDate() {
        return xyzDate;
    }

    public void setXyzDate(XMLGregorianCalendar xyzDate) {
        this.xyzDate = xyzDate;
    }

}

选项#2 - 使用MOXy的@XmlNullPolicy扩展

MOXy提供了一个@XmlNullPolicy扩展,使您可以在如何表示null方面获得一些灵活性。

package forum11743306;

import javax.xml.bind.annotation.*;
import javax.xml.datatype.XMLGregorianCalendar;

import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement
public class Root {

    private XMLGregorianCalendar xyzDate;

    @XmlElement(name = "XYZDate", required=true, nillable = true)
    @XmlNullPolicy(emptyNodeRepresentsNull = true, nullRepresentationForXml = XmlMarshalNullRepresentation.EMPTY_NODE)
    public XMLGregorianCalendar getXyzDate() {
        return xyzDate;
    }

    public void setXyzDate(XMLGregorianCalendar xyzDate) {
        this.xyzDate = xyzDate;
    }

}

其他文件

以下文件可与任一选项一起使用,以完成示例。

jaxb.properties

要将MOXy指定为您的JAXB提供程序,您需要在与域模型相同的包中包含一个名为jaxb.properties的文件,并具有以下条目(参见:http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html)。

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

package forum11743306;

import javax.xml.bind.*;
import javax.xml.datatype.DatatypeFactory;

import org.eclipse.persistence.Version;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);
        System.out.println(Version.getVersion());
        System.out.println(jc.getClass());

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        Root root = new Root();
        root.setXyzDate(null);
        marshaller.marshal(root, System.out);

        root.setXyzDate(DatatypeFactory.newInstance().newXMLGregorianCalendar("2012-08-01"));
        marshaller.marshal(root, System.out);
    }

}

输出

2.4.0
class org.eclipse.persistence.jaxb.JAXBContext
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <XYZDate/>
</root>
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <XYZDate>2012-08-01</XYZDate>
</root>

1
@AmitTelang - 我很乐意帮助。您可以使用Eclipse Europa,但是Java EE版本的Eclipse Juno具有一些有趣的JAXB工具,并且可能值得尝试新版本。 - bdoughan
@AmitTelang - 你修改过演示吗?你能通过我的联系页面(http://blog.bdoughan.com/p/contact_01.html)开始一封电子邮件线程吗?这样我可以更好地帮助你。 - bdoughan
嗨,布莱斯,我给你发了电子邮件,请查看一下。 - Amit Telang
演示类的结果:当设置空值时,没有</XYZDate>标签。 <?xml version="1.0" encoding="UTF-8"?> <root/> <?xml version="1.0" encoding="UTF-8"?> <root> <XYZDate>2012-08-01T19:23:39.427</XYZDate> </root> - Amit Telang
嗨Blaise, 我们已经给您发送了一个样例应用程序,根据这个应用程序,在使用eclipselink.jar版本2.4.0时,生成的XML文件会带有一个额外的标签ns0。 请仔细查看并就此向我们提供建议,因为我们不想要那个额外的标签。 - Amit Telang
显示剩余5条评论

4
您应该阅读 nillable和minOccurs XSD元素属性,因为在XML中,nilempty元素之间的区别很重要。即xsi:nil=true类似于SQL NULL,但是空元素表示存在一个空元素。 :)
我知道这很令人困惑。
为了解决您特定的问题,如果您正在使用JAXB序列化来生成它,请阅读如何使用JAXB实例化一个空元素。该问题本身向您展示了如何生成一个空元素。

2
Blaise的回答不错,但已经过时了。有一种更好、更简单的方法可以达到相同的效果。我在许多论坛上搜索并结合不同的解决方案得出了这个方法。我在这里分享,希望对他人有所帮助。

注意:下面的解决方案比仅适用于日期更为通用。


方法一:如果您想在XML中将所有空值替换为空字符串

会话事件适配器类

将下面的类添加到代码中一个方便的包中。

package com.dev

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.oxm.mappings.XMLDirectMapping;
import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType;
import org.eclipse.persistence.sessions.*;

public class NullPolicySessionEventListener extends SessionEventAdapter {

@Override
public void preLogin(SessionEvent event) {
    Project project = event.getSession().getProject();
    for(ClassDescriptor descriptor : project.getOrderedDescriptors()) {
        for(DatabaseMapping mapping : descriptor.getMappings()) {
            if(mapping.isAbstractDirectMapping()) {
                XMLDirectMapping xmlDirectMapping = (XMLDirectMapping) mapping;
                xmlDirectMapping.getNullPolicy().setMarshalNullRepresentation(XMLNullRepresentationType.EMPTY_NODE);
                xmlDirectMapping.getNullPolicy().setNullRepresentedByEmptyNode(true);
            }
        }
    }
 }

}

Entity Class

package com.dev;

import javax.xml.bind.annotation.*;

import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement(name = "Entity")
public class Entity {

    @XmlElement(name = "First_Name", required=true, nillable = true)
    private String firstName;

    @XmlElement(name = "Last_Name" , required=true, nillable = true)
    private String lastName;

    public Entity(){}

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

DemoApp类

package com.dev;

import javax.xml.bind.*;
import org.eclipse.persistence.*;
import java.util.Map;
import java.util.HashMap;

public class DemoApp {

public static void main(String[] args) throws Exception {
    Map<String, Object> properties = new HashMap<String,Object>(1);
    SessionEventListener sessionEventListener = new NullSessionEventListener();
    properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
    JAXBContext context = JAXBContextFactory.createContext(new Class[] {ListofEntities.class, list.get(0).getClass()},properties);
    Marshaller marshaller = jc.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    Entity entity = new Entity();
    entity.setfirstName(null);
    entity.setLastName(null);
    marshaller.marshal(entity, System.out);

    entity.setfirstName("Ramu");
    entity.setLastName("K");
    marshaller.marshal(entity, System.out);
}

}

输出:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <First_Name/>
   <Last_Name/>
</root>
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <First_Name>Ramu</First_Name>    
   <Last_Name>Ramu</Last_Name>   
</root>

方法二:如果您只想在xml中替换选定的空值为空字符串

实体类

package com.dev;

import javax.xml.bind.annotation.*;

import org.eclipse.persistence.oxm.annotations.*;

@XmlRootElement(name = "Entity")
public class Entity {

    @XmlElement(name = "First_Name", required=true, nillable = true)
    @XmlNullPolicy(emptyNodeRepresentsNull = true, nullRepresentationForXml = XmlMarshalNullRepresentation.EMPTY_NODE)
    private String firstName;

    @XmlElement(name = "Last_Name" , required=true, nillable = true)
    private String lastName;

    public Entity(){}

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

DemoApp类

package com.dev;

import javax.xml.bind.*;
import org.eclipse.persistence.*;

public class DemoApp {

public static void main(String[] args) throws Exception {
    JAXBContext context = JAXBContextFactory.createContext(new Class[] {ListofEntities.class, list.get(0).getClass()},null);
    Marshaller marshaller = jc.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    Entity entity = new Entity();
    entity.setfirstName(null);
    entity.setLastName(null);
    marshaller.marshal(entity, System.out);

    entity.setfirstName("Ramu");
    entity.setLastName("K");
    marshaller.marshal(entity, System.out);
}

输出:

在这个输出中,我们只看到带有XmlNullPolicy注释的元素在值为null时被显示。其他元素由于jaxb的默认行为而被省略。

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <First_Name/>
</root>
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <First_Name>Ramu</First_Name>    
   <Last_Name>Ramu</Last_Name>   
</root>

参考文献:

  1. jaxb.properties文件应该放在哪里?

  2. 如何在XML JAXB中将null值表示为空元素


0

我投了gbvb的答案。

我不明白你为什么要这样做。

带有xmlns:xsixsi:nil的空元素是正确的方法。

如果没有这些属性,任何合理的解析器都会给出空字符串,即使元素是自闭合的。

比如说,你想给客户端一个整数值,表示许多玩家得分中的最高分。

当你可以计算时,你可以给出正确的值。 当实际上没有玩家得分时,你应该将正确的值设为NULLnil,表示没有累积记录。

<highestScore among="128">98</highestScore>

可以说最高分是在128次尝试中的98分。

而且

<highestScore among="0"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:nil="true"/>

可以说没有最高分,因为没有记录分数。

但是

<highestScore/>

这只是一个简单的自闭空元素,没有任何意义。


实际上,我正在为第三方创建XML,但他们的模型不理解xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance",只能理解<highestScore/>。由于需要遵循第三方模型,因此才会出现这个问题。 - Amit Telang
我明白。我希望第三方只是不理解 xmlns:xsi,而不是抛出异常。这只是一个属性。 - Jin Kwon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接