【问题标题】:Marshalling nested objects to a "flat" XML structure将嵌套对象编组为“平面”XML 结构
【发布时间】:2012-06-13 03:26:18
【问题描述】:

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

例如,给定:

                                                    (A)
public class Customer {

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

public class Address {

    protected String street;
    protected String city;
}

使用通常的 JAXB 注释会导致

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

但是,相反,我需要编组与

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

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

  1. (A) 中的 Java 类由 JAXB 从与 (B) 中的 XML 结构对应的现有模式生成。我不希望维护生成的类的修改版本。
  2. 我不拥有或维护上述架构。实际的模式非常大,并且经常会进行细微的修改。想出和维护一个等效的模式是很乏味的。此外,为了跟上架构修改,我依赖 JAXB 自动生成类。
  3. 如果它使事情变得更容易,嵌套最多只有一层。在示例中,Address 不会包含任何其他复杂类型。

我正在查看来自 MOXy 的 @XmlPath 注释,但我不知道如何获取以 (C) 为前缀的节点名称。

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

【问题讨论】:

    标签: java xml jaxb


    【解决方案1】:

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

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

    外部映射文件 (oxm.xml)

    如果您的模型类位于名为 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>
    

    演示

    以下代码演示了在创建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

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

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

    c.xml/输出

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

    【讨论】:

    • 我有点担心必须为映射编写另一个 xml 文件。原始类来自的 XSD 已经非常大,并且看起来此类的映射也相当大。考虑到我需要的映射是机械的,有没有办法在 MOXy 中以编程方式提供这些映射?
    • @Edward - 这里的映射文件只需要覆盖元数据,所以我预计它会比原始 XSD 小得多。有诸如 @XmlCustomizer 和 SessionEventListener 之类的钩子,可以让您处理底层元数据并以编程方式对其进行修改。
    • 你在@XmlCustomizer 和SessionEventListener 迷失了我(我是不是在找对地方?)我以前从未使用过 MOXy。我可能需要一段时间才能理解它。
    【解决方案2】:

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

    【讨论】:

    • 正如我所提到的,这些类是从相当大的 XML 模式中自动生成的。因此,手动维护第二组类将是令人望而却步的。另外,当我开始编写反射代码时,我最好写出我想要的 XML,而不是写到另一个类。谢谢。
    【解决方案3】:

    注意:我是 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" />
    

    通过这种方式,将嵌套 bean 编组为平面 XML 结构很容易。而反之亦然 - 解组 - 将按预期创建对象图。

    查看 Juffrou-XML here。

    【讨论】:

      【解决方案4】:

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

      虽然以 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】:

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-01-15
            • 2018-04-17
            • 1970-01-01
            相关资源
            最近更新 更多