使用 @XmlPath 和 jaxb/MOXy 映射复杂类型

2022-01-19 00:00:00 xml java jaxb moxy

我有一个深度 XML 结构,其中包含许多无意义的包装器,我将它们映射到单个 Java 类.用@XmlPath 映射简单的数据类型是在公园里散步,但是当涉及到实际上需要自己的类的类型时,我不太确定如何去做,尤其是当这些类型也应该放在一个列表中时.

I have a deep XML structure with a lot of pointless wrappers I'm mapping to a single Java class. Mapping the simple datatypes with @XmlPath is a walk in the park, but when it comes to types that actually require their own class I'm not quite sure how to do it, especially when those types should be put in a list as well.

我在将以下示例中的所有 element 类型映射到我的 Element 类时遇到问题.由于 elements 包装器位于使用 @XmlPath 映射的资源中,因此我不能使用 @XmlElementWrapper,否则我通常会采用这种方式会这样做.

I'm having problems to map all the element types in the below example to my Element class. Since the elements wrapper resides in resource which is mapped using @XmlPath I can not use the @XmlElementWrapper, which would otherwise be the way I usually would do this.

示例 XML 结构

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<s:root xsi:schemaLocation="http://www.example.eu/test ResourceSchema.xsd" xmlns:s="http://www.example.eu/test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <s:resource>
        <s:information>
            <s:date>2013-07-04</s:date>
            <s:name>This example does not work</s:name>
        </s:information>
        <s:elements>
            <s:refobj>
                <s:id>1</s:id>
                <s:source>First Source</s:source>
            </s:refobj>
            <s:refobj>
                <s:id>2</s:id>
                <s:source>Second Source</s:source>
            </s:refobj>
            <s:refobj>
                <s:id>5</s:id>
                <s:source>Fifth Source</s:source>
            </s:refobj>
        </s:elements>
    </s:resource>
</s:root>

Root.java

@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlPath("resource/information/date/text()")
    private String date;

    @XmlPath("s:resource/s:information/s:name/text()")
    private String name;

    @XmlPath("resource/elements/refobj")
    private List<RefObj> refObjs;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

RefObj.java

@XmlRootElement(name = "refobj")
@XmlAccessorType(XmlAccessType.FIELD)
public class RefObj {

    @XmlElement(name = "id")
    private int id;

    @XmlElement(name = "source")
    private String source;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

}

编组器/解组器

public static void main(String[] args) {
    String xml = getXML();

    Root root = null;
    try {
        JAXBContext context = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = context.createUnmarshaller();

        StringReader stringReader = new StringReader(xml);

        root = (Root) unmarshaller.unmarshal(stringReader);
    } catch (Exception ex) {
        System.err.println("Failed to unmarshal XML!");
    }

    try {
        JAXBContext context = JAXBContext.newInstance(Root.class);
        Marshaller marshaller = context.createMarshaller();

        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "http://www.example.eu/test ResourceSchema.xsd");

        StringWriter stringWriter = new StringWriter();
        marshaller.marshal(root, stringWriter);

        System.out.println(new String(stringWriter.toString().getBytes(Charset.forName("UTF-8"))));
    } catch (Exception ex) {
        System.err.println("Failed to marshal object!");
    }

}

package-info.java

@XmlSchema(
        namespace = "http://www.example.eu/test",
        attributeFormDefault = XmlNsForm.QUALIFIED,
        elementFormDefault = XmlNsForm.QUALIFIED,
        xmlns = {
    @XmlNs(
            prefix = "s",
            namespaceURI = "http://www.example.eu/test")
},
        location = "http://www.example.eu/test ResourceSchema.xsd")
package se.example.mavenproject1;

import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

我可以执行应用程序,但是当我解组/编组 XML 内容时没有映射任何元素,而是得到一个仅包含信息的 XML 表示.

I can execute the application, but no element is mapped when I unmarshal/marshal the XML content, instead I get a XML representation containing just the information.

更新

在发布上一个示例后,我意识到它实际上按预期工作,这让我更加困惑.尽管我试图在我的生产代码中复制(以前的)工作示例但没有任何成功,尽管我已经设法将我遇到的问题实际引入到示例代码中.由于我需要为出现问题添加命名空间,因此我假设它与命名约定和 X(ml)Path 有关.

After posting the previous example I realized that it actually worked as intended, which made me even more confused. Although I've tried to replicate the (previously) working example in my production code without any success, although I've managed to actually introduce the problems I'm having into the example code. Since I needed to add a namespace for the problems to appear I'm assuming it has something to do with naming conventions and X(ml)Path.

我还添加了 package-info.java 和我在处理这些对象时使用的编组器/解组器.由于 jaxb.properties 不包含任何令人兴奋的内容,因此我将其省略了.

I've also added package-info.java and the marshaller/unmarshaller I'm using when working with those objects. Since the jaxb.properties doesn't contain anything exciting I've left it out.

推荐答案

当我运行您的示例时,一切正常.由于您的真实模型可能具有 get/set 方法,您需要确保将 @XmlAccessorType(XmlAccessType.FIELD) 添加到您的类中,否则 MOXy(或任何其他 JAXB impl)也会处理相应的属性被映射(参见:http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html).

When I ran your example everything worked fine. Since your real model probably has get/set methods you will need to ensure that you add @XmlAccessorType(XmlAccessType.FIELD) to your class otherwise MOXy (or any other JAXB impl) will also treat the corresponding properties as being mapped (see: http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html).

import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlPath("resource/information/date/text()")
    private String date;

    @XmlPath("resource/information/name/text()")
    private String name;

    @XmlPath("resource/elements/element")
    private List<Element> elements;

}

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

You also need to ensure that you have a jaxb.properties file in the same package as your domain model with the following entry to specify MOXy as your JAXB provider (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).

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

更多信息

  • http://blog.bdoughan.com/2010/07/xpath-based-mapping.html

更新 #1

当您的文档是命名空间限定时,@XmlPath 注释需要考虑这一点.路径中的节点可以根据 @XmlSchema 注释限定.

When your document is namespace qualified the @XmlPath annotation needs to factor this in. The nodes in the path can be qualified according to the @XmlSchema annotation.

包裹信息

在您的 package-info 类中,前缀 s 分配给命名空间 URI http://www.example.eu/test.

In your package-info class the prefix s is assigned to the namespace URI http://www.example.eu/test.

@XmlSchema(
        namespace = "http://www.example.eu/test",
        attributeFormDefault = XmlNsForm.QUALIFIED,
        elementFormDefault = XmlNsForm.QUALIFIED,
        xmlns = {
    @XmlNs(
            prefix = "s",
            namespaceURI = "http://www.example.eu/test")
},
        location = "http://www.example.eu/test ResourceSchema.xsd")
package se.example.mavenproject1;

import javax.xml.bind.annotation.XmlNs;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

这意味着具有 http://www.example.eu/test 命名空间的节点应该在 @XmlPath<中具有前缀 s/code> 注释.

This means that the nodes qualified with the http://www.example.eu/test namespace should have the prefix s in the @XmlPath annotation.

import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlPath("s:resource/s:information/s:date/text()")
    private String date;

    @XmlPath("s:resource/s:information/s:name/text()")
    private String name;

    @XmlPath("s:resource/s:elements/s:element")
    private List<Element> elements;

}

<小时>

更新 #2

所以似乎需要在@XmlPath 中指定命名空间将路径映射到复杂对象时,但可以跳过将路径映射到简单对象,例如字符串或整数.

So it seems as if the namespace needs to be specified in the @XmlPath when mapping the path to a complex object, but can be skipped when mapping the path to a simple object such as a String or an integer.

这是一个错误.您应该以与复杂对象相同的方式对简单对象的 @XmlPath 进行命名空间限定(参见更新 #1).正确的映射今天有效,我们将修复以下错误,以便不正确的映射正确运行.您可以使用下面的链接来跟踪我们在该问题上的进展:

This is a bug. You should namespace qualify the @XmlPath for simple objects the same way you do for complex objects (see UPDATE #1). The correct mapping works today, we will fix the following bug so that the incorrect mapping behaves correctly. You can use the link below to track our progress on that issue:

  • http://bugs.eclipse.org/412311

相关文章