将嵌套对象编组为“扁平”XML结构

时间:2022-04-12 14:27:55

How might I marshall an object hierarchy such that, instead of having the component objects becoming nested XML elements, their properties become direct children of the root element with their names prefixed by their type.

我如何编组一个对象层次结构,使得它们的属性不再使组件对象成为嵌套的XML元素,而是成为根元素的直接子元素,其名称以其类型为前缀。

For example, given:

例如,给定:

                                                    (A)
public class Customer {

    protected String firstName;
    protected String lastName;
    protected Address address;
}

public class Address {

    protected String street;
    protected String city;
}

Using the usual JAXB annotations would result in

使用通常的JAXB注释会导致

                                                    (B)
<customer>
    <firstName>Juan</firstName>
    <lastName>dela Cruz</lastName>
    <address>
        <street>123 Rizal Avenue</street>
        <city>Manila</city>
    </address>
</customer>

But, instead, I need to marshall the same as

但是,相反,我需要编组

                                                    (C)
<customer>
    <firstName>Juan</firstName>
    <lastName>dela Cruz</lastName>
    <address_street>123 Rizal Avenue</address_street>
    <address_city>Manila</address_city>
</customer>

It would be great if there were some JAXB incantation to solve my needs since I'm already using JAXB for most of the things around this problem. In fact, these present some constraints to my specific situation:

如果有一些JAXB咒语来解决我的需求会很好,因为我已经在使用JAXB解决了这个问题的大部分问题。事实上,这些对我的具体情况有一些限制:

  1. The Java classes in (A) are generated by JAXB from an existing schema that corresponds to the XML structure in (B). I would prefer not to maintain modified versions of the generated classes.
  2. (A)中的Java类由JAXB从与(B)中的XML结构对应的现有模式生成。我宁愿不维护生成的类的修改版本。

  3. I do not own or maintain the said schema. The actual schema is quite large and is often subject to minor modifications. It would be tedious to come up with and maintain an equivalent schema. Also, to keep up with schema modifications, I rely on the automated class generation by JAXB.
  4. 我不拥有或维护上述架构。实际架构非常大,通常会进行微小的修改。提出并维护一个等效的模式是很乏味的。另外,为了跟上模式修改,我依赖于JAXB的自动化类生成。

  5. If it makes things easier, nesting is only up to one level deep. In the example, Address would not contain any other complex type.
  6. 如果它使事情变得更容易,嵌套只能达到一个深度。在示例中,Address不包含任何其他复杂类型。

I'm looking at the @XmlPath annotation from MOXy, but I can't figure out how to get the node names prefixed as in (C).

我正在查看来自MOXy的@XmlPath注释,但我无法弄清楚如何将节点名称作为(C)中的前缀。

I dream of a solution where some xjc customizations providing the right JAXB annotations get me going, but that's looking unlikely from my searches so far. Any non-JAXB solution would suffice.

我梦想有一个解决方案,其中一些xjc自定义提供正确的JAXB注释让我前进,但到目前为止我的搜索看起来不太可能。任何非JAXB解决方案都足够了。

6 个解决方案

#1


1  

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

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

You can use the external mapping file in EclipseLink JAXB (MOXy) to apply a second mapping to your object model, and the @XmlPath extension to flatten the object model.

您可以使用EclipseLink JAXB(MOXy)中的外部映射文件将第二个映射应用于对象模型,并使用@XmlPath扩展来展平对象模型。

External Mapping File (oxm.xml)

外部映射文件(oxm.xml)

The following is what the external mapping file might look like if your model classes where in a package called forum11007814.

如果您的模型类在一个名为forum11007814的包中,则以下是外部映射文件的外观。

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum11007814"
    xml-accessor-type="FIELD">
    <java-types>
        <java-type name="Customer">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="address" xml-path="."/>
            </java-attributes>
        </java-type>
        <java-type name="Address">
            <java-attributes>
                <xml-element java-attribute="street" name="address_street"/>
                <xml-element java-attribute="city" name="address_city"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

Below is code that demonstrates how to leverage MOXy's external mapping file when creating the JAXBContext.

下面的代码演示了如何在创建JAXBContext时利用MOXy的外部映射文件。

package forum11007814;

import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String,Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum11007814/oxm.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);

        File xml = new File("src/forum11007814/c.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

jaxb.properties

To specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry.

要将MOXy指定为JAXB提供程序,您需要在与域模型相同的包中添加名为jaxb.properties的文件,并使用以下条目。

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

c.xml/Output

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Juan</firstName>
   <lastName>dela Cruz</lastName>
   <address_street>123 Rizal Avenue</address_street>
   <address_city>Manila</address_city>
</customer>

#2


0  

I'm pretty sure there's no standard way of doing this. What you can do is create two java classes, one with the hierarchical structure, one with the flat structure, and use java introspection to copy the data from one into the other, then use the second to create the xml.

我很确定没有标准的方法可以做到这一点。你可以做的是创建两个java类,一个具有层次结构,一个具有平面结构,并使用java introspection将数据从一个复制到另一个,然后使用第二个创建xml。

#3


0  

Note: I am the creator of juffrou-xml which enables you to do that in a simple way: just configure this in xml-mapping file:

注意:我是juffrou-xml的创建者,它允许您以一种简单的方式执行此操作:只需在xml-mapping文件中配置它:

<root-element xml="Customer" type="org.yourpackage.Customer">
  <element property="firstName" />
  <element property="lastName" />
  <element property="address.street" xml="address_street"/>
  <element property="address.city" xml="address_city"/>
</root-element>

<root-element xml="Address" type="org.yourpackage.Address" />

Marshalling nested beans to a flat XML struture is easy this way. And the other way around - unmarshalling - will create the object graph as expected.

通过这种方式将嵌套bean编组为扁平的XML结构非常容易。反过来 - 解组 - 将按预期创建对象图。

Check out Juffrou-XML here.

在这里查看Juffrou-XML。

#4


0  

I solved this using an XStream Converter. It checks for the @XmlType annotation to determine if a JAXB bean is being converted. All other types go through the default converters.

我使用XStream转换器解决了这个问题。它检查@XmlType批注以确定是否正在转换JAXB bean。所有其他类型都通过默认转换器。

Though a JAXB-centric solution would have been nice, XStream provided a compellingly straightforward solution.

虽然以JAXB为中心的解决方案本来不错,但XStream提供了一个引人注目的直接解决方案。

public class FlatXmlConverter implements Converter {

    private static final Logger log =
            LoggerFactory.getLogger(NvpConverter.class);

    @Override
    public void marshal(Object source, HierarchicalStreamWriter writer,
            MarshallingContext context) {
        Class<? extends Object> sourceClass = source.getClass();
        String prefix = (String) context.get("prefix");
        for (Field field : sourceClass.getDeclaredFields()) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            String name = field.getName();
            Class<?> type = field.getType();

            try {
                Object value = field.get(source);
                if (value != null) {
                    if (type.isAnnotationPresent(XmlType.class)) {
                        context.put("prefix", name);
                        context.convertAnother(value);
                        context.put("prefix", null);
                    } else {
                        String nodeName;
                        if (prefix == null) {
                            nodeName = name;
                        } else {
                            nodeName = prefix + "_" + name;
                        }

                        writer.startNode(nodeName);
                        context.convertAnother(value);
                        writer.endNode();
                    }
                }
            } catch (IllegalArgumentException ex) {
                log.error("IllegalArgumentException", ex);
            } catch (IllegalAccessException ex) {
                log.error("IllegalAccessException", ex);
            }
        }
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public boolean canConvert(Class type) {
        log.debug("canConvert({})", type);
        return type.isAnnotationPresent(XmlType.class);
    }
}

#5


-1  

Have you checked JiBX (tool for binding XML data to Java objects)? http://jibx.sourceforge.net/binding/tutorial/binding-structures.html

你检查过JiBX(用于将XML数据绑定到Java对象的工具)吗? http://jibx.sourceforge.net/binding/tutorial/binding-structures.html

#6


-1  

Use @XmlID a @XmlIDREF. And make class with list of all objects.

使用@XmlID @XmlIDREF。并使用所有对象的列表创建类。

#1


1  

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

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

You can use the external mapping file in EclipseLink JAXB (MOXy) to apply a second mapping to your object model, and the @XmlPath extension to flatten the object model.

您可以使用EclipseLink JAXB(MOXy)中的外部映射文件将第二个映射应用于对象模型,并使用@XmlPath扩展来展平对象模型。

External Mapping File (oxm.xml)

外部映射文件(oxm.xml)

The following is what the external mapping file might look like if your model classes where in a package called forum11007814.

如果您的模型类在一个名为forum11007814的包中,则以下是外部映射文件的外观。

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum11007814"
    xml-accessor-type="FIELD">
    <java-types>
        <java-type name="Customer">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="address" xml-path="."/>
            </java-attributes>
        </java-type>
        <java-type name="Address">
            <java-attributes>
                <xml-element java-attribute="street" name="address_street"/>
                <xml-element java-attribute="city" name="address_city"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

Below is code that demonstrates how to leverage MOXy's external mapping file when creating the JAXBContext.

下面的代码演示了如何在创建JAXBContext时利用MOXy的外部映射文件。

package forum11007814;

import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String,Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum11007814/oxm.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);

        File xml = new File("src/forum11007814/c.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

jaxb.properties

To specify MOXy as your JAXB provider you need to add a file called jaxb.properties in the same package as your domain model with the following entry.

要将MOXy指定为JAXB提供程序,您需要在与域模型相同的包中添加名为jaxb.properties的文件,并使用以下条目。

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

c.xml/Output

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Juan</firstName>
   <lastName>dela Cruz</lastName>
   <address_street>123 Rizal Avenue</address_street>
   <address_city>Manila</address_city>
</customer>

#2


0  

I'm pretty sure there's no standard way of doing this. What you can do is create two java classes, one with the hierarchical structure, one with the flat structure, and use java introspection to copy the data from one into the other, then use the second to create the xml.

我很确定没有标准的方法可以做到这一点。你可以做的是创建两个java类,一个具有层次结构,一个具有平面结构,并使用java introspection将数据从一个复制到另一个,然后使用第二个创建xml。

#3


0  

Note: I am the creator of juffrou-xml which enables you to do that in a simple way: just configure this in xml-mapping file:

注意:我是juffrou-xml的创建者,它允许您以一种简单的方式执行此操作:只需在xml-mapping文件中配置它:

<root-element xml="Customer" type="org.yourpackage.Customer">
  <element property="firstName" />
  <element property="lastName" />
  <element property="address.street" xml="address_street"/>
  <element property="address.city" xml="address_city"/>
</root-element>

<root-element xml="Address" type="org.yourpackage.Address" />

Marshalling nested beans to a flat XML struture is easy this way. And the other way around - unmarshalling - will create the object graph as expected.

通过这种方式将嵌套bean编组为扁平的XML结构非常容易。反过来 - 解组 - 将按预期创建对象图。

Check out Juffrou-XML here.

在这里查看Juffrou-XML。

#4


0  

I solved this using an XStream Converter. It checks for the @XmlType annotation to determine if a JAXB bean is being converted. All other types go through the default converters.

我使用XStream转换器解决了这个问题。它检查@XmlType批注以确定是否正在转换JAXB bean。所有其他类型都通过默认转换器。

Though a JAXB-centric solution would have been nice, XStream provided a compellingly straightforward solution.

虽然以JAXB为中心的解决方案本来不错,但XStream提供了一个引人注目的直接解决方案。

public class FlatXmlConverter implements Converter {

    private static final Logger log =
            LoggerFactory.getLogger(NvpConverter.class);

    @Override
    public void marshal(Object source, HierarchicalStreamWriter writer,
            MarshallingContext context) {
        Class<? extends Object> sourceClass = source.getClass();
        String prefix = (String) context.get("prefix");
        for (Field field : sourceClass.getDeclaredFields()) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            String name = field.getName();
            Class<?> type = field.getType();

            try {
                Object value = field.get(source);
                if (value != null) {
                    if (type.isAnnotationPresent(XmlType.class)) {
                        context.put("prefix", name);
                        context.convertAnother(value);
                        context.put("prefix", null);
                    } else {
                        String nodeName;
                        if (prefix == null) {
                            nodeName = name;
                        } else {
                            nodeName = prefix + "_" + name;
                        }

                        writer.startNode(nodeName);
                        context.convertAnother(value);
                        writer.endNode();
                    }
                }
            } catch (IllegalArgumentException ex) {
                log.error("IllegalArgumentException", ex);
            } catch (IllegalAccessException ex) {
                log.error("IllegalAccessException", ex);
            }
        }
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public boolean canConvert(Class type) {
        log.debug("canConvert({})", type);
        return type.isAnnotationPresent(XmlType.class);
    }
}

#5


-1  

Have you checked JiBX (tool for binding XML data to Java objects)? http://jibx.sourceforge.net/binding/tutorial/binding-structures.html

你检查过JiBX(用于将XML数据绑定到Java对象的工具)吗? http://jibx.sourceforge.net/binding/tutorial/binding-structures.html

#6


-1  

Use @XmlID a @XmlIDREF. And make class with list of all objects.

使用@XmlID @XmlIDREF。并使用所有对象的列表创建类。