【问题标题】:Use Jaxb to unmarshall part of XML to an instance of org.jdom.Element使用 Jaxb 将部分 XML 解组为 org.jdom.Element 的实例
【发布时间】:2015-11-12 21:36:05
【问题描述】:

我正在使用严重依赖于 org.jdom 项目 (http://mvnrepository.com/artifact/org.jdom/jdom/1.1.3) 版本 1 和手动构建的 XML 的遗留产品,但我想尽可能多地使用 Jaxb。我们使用 Moxy 作为 Jaxb 实现。

所以说我有以下xml:

<foo bar="bar">
    <baz>
        <test value="something" />
    </baz>
</foo>

由于遗留代码使用 baz 元素作为 org.jdom.Element 我想让 Jaxb 将内部元素“baz”解组为 org.jdom.Element 但解组中的 v 始终是一个空字符串(“ ") 所以 baz 变为空。 我在下面创建了一个示例,我尝试使用 XmlAdapter 但我无法让它工作。

import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

public class Test {

  public static void main(String[] args) throws JAXBException {

    String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
    JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
            .createContext(new Class<?>[] {Foo.class}, null);
    javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    Foo foo = unmarshaller
            .unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
            .getValue();

    System.out.println(foo);
  }

  @XmlRootElement
  private static final class Foo {

    @XmlAttribute
    public String bar;

    @XmlElement
    @XmlJavaTypeAdapter(ElementAdapter.class)
    public Element baz;

    @Override
    public String toString() {
        return "Foo [bar=" + bar + ", baz=" + baz + "]";
    }

  }

  private static final class ElementAdapter extends XmlAdapter<String, Element> {

    @Override
    public Element unmarshal(String v) throws Exception {
        Document document = new SAXBuilder().build(new StringReader(v));
        return document.getRootElement();
    }

    @Override
    public String marshal(Element v) throws Exception {
        return new XMLOutputter(org.jdom.output.Format.getPrettyFormat()).outputString(v);
    }

  }

}

也许我从错误的角度来攻击它。关于如何实现这一点的任何建议?

【问题讨论】:

    标签: java xml jaxb moxy


    【解决方案1】:

    在解组时调试问题的一个好方法是使用javax.xml.bind.helpers.DefaultValidationEventHandler

    在您的情况下,您可以简单地将其添加到 main 方法中的解组器中,例如

    // ...
    javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
    unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
    // ...
    

    当您在附加了验证处理程序的情况下按原样运行测试程序时,您会看到类似以下内容,这基本上是在告诉您没有与 XML 中的 test 元素对应的 Java 类。

    [Exception [EclipseLink-25004] (Eclipse Persistence Services - 2.6.1.v20150916-55dc7c3): org.eclipse.persistence.exceptions.XMLMarshalException
    Exception Description: An error occurred unmarshalling the document
    Internal Exception: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 47; unexpected element (uri:"", local:"test"). Expected elements are (none)]
    

    我认为您假设您的 ElementAdapter 实现将读取并使用 baz 节点和所有子节点作为字符串。实际上,我认为(分页 Blaise Doughan?)发生的事情是 JAXB 正在对 XML 树进行一些预遍历,在此期间它发现您没有 test 的模型,随后丢弃 test 及其属性。


    要了解这里发生了什么,首先要简化您的模型,不要担心 JDOM 元素转换。请注意,我已经更改了您的顶级类的名称,因此它不会与 Test 模型类冲突:

    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.transform.stream.StreamSource;
    import java.io.ByteArrayInputStream;
    import java.nio.charset.StandardCharsets;
    
    public class Main {
    
        public static void main(String[] args) throws JAXBException {
            String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
            JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
                    .createContext(new Class<?>[] {Foo.class}, null);
            javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
            Foo foo = unmarshaller
                    .unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
                    .getValue();
    
            System.out.println(foo);
        }
    
        @XmlRootElement()
        public static class Foo {
    
            @XmlAttribute
            public String bar;
    
            @XmlElement
            public Baz baz;
    
            @Override
            public String toString() {
                return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
            }
        }
    
        @XmlRootElement
        public static class Baz {
    
            @XmlElement
            public Test test;
    
            @Override
            public String toString() {
                return "<baz>" + test.toString() + "</baz>";
            }
        }
    
        @XmlRootElement
        public static class Test {
            @XmlAttribute
            public String value;
    
            @Override
            public String toString() {
                return "<test value=\"" + value + "\"/>";
            }
        }
    
    }
    

    这应该正确解组并打印&lt;foo bar="bar"&gt;&lt;baz&gt;&lt;test value="something"/&gt;&lt;/baz&gt;&lt;/foo&gt;。另请注意,通常您将传递 JAXBContextFactory 一个包含整个对象图的 ObjectFactory,该对象图将从您的 XML 模式生成。这可能是混乱的根源。


    现在您可以将XmlAdapter 添加回来,但不要尝试将String 转换为Element,而是将Baz 转换为Element

    import org.jdom.Document;
    import org.jdom.Element;
    import org.jdom.input.SAXBuilder;
    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.Marshaller;
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    import javax.xml.transform.stream.StreamSource;
    import java.io.ByteArrayInputStream;
    import java.io.StringReader;
    import java.io.StringWriter;
    import java.nio.charset.StandardCharsets;
    
    public class Main {
    
        public static void main(String[] args) throws JAXBException {
            String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
            JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
                    .createContext(new Class<?>[] {Foo.class}, null);
            javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
            Foo foo = unmarshaller
                    .unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
                    .getValue();
    
            System.out.println(foo);
        }
    
        @XmlRootElement()
        public static class Foo {
    
            @XmlAttribute
            public String bar;
    
            @XmlElement
            @XmlJavaTypeAdapter(ElementAdapter.class)
            public Element baz;
    
            @Override
            public String toString() {
                return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
            }
        }
    
        @XmlRootElement
        public static class Baz {
    
            @XmlElement
            public Test test;
    
            @Override
            public String toString() {
                return "<baz>" + test.toString() + "</baz>";
            }
        }
    
        @XmlRootElement
        public static class Test {
            @XmlAttribute
            public String value;
    
            @Override
            public String toString() {
                return "<test value=\"" + value + "\"/>";
            }
        }
    
        public static class ElementAdapter extends XmlAdapter<Baz, Element> {
    
            @Override
            public Element unmarshal(Baz baz) throws Exception {
                StringWriter sw = new StringWriter();
                // Note: it is a terrible idea to re-instantiate a context here
                // Use a cached value or a singleton from before
                JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
                        .createContext(new Class<?>[] {Baz.class}, null);
                javax.xml.bind.Marshaller m = jaxbContext.createMarshaller();
                m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
                m.marshal(baz, sw);
    
                Document document = new SAXBuilder().build(new StringReader(sw.toString()));
                return document.getRootElement();
            }
    
            @Override
            public Baz marshal(Element v) throws Exception {
                // TODO implement this
                return null;
            }
    
        }
    }
    

    这或多或少应该可以满足您的需求,尽管您可能必须在转换为 JDOM 之前清理适配器中的某些 Baz 元素(去掉 XML 前导码等)。请注意,在 ElementAdapter 中实例化另一个 JAXBContext 可能是一个糟糕的主意,并且可能有一种更有效的方法可以将 JAXB 元素转换为 JDOM 元素。这段代码很容易在调试器中单步执行,因此您可以看到发生了什么。

    【讨论】:

    • 非常感谢@justingarrick 的回复! DefaultValidationEventHandler 是一个很好的建议,可以帮助调试事情以及将来真正有用的事情!可能不清楚的一件事是 baz 可以包含任何内容,我并不真正控制也不关心内容,所以我实际上最终用一个非常简单的 XmlAdapter 解决了它。我会将其发布为帮助他人和反馈的答案。
    【解决方案2】:

    我想我会分享一个对我有用的解决方案。

    Jaxb: how to unmarshall xs:any XML-string part? 这篇文章的启发,它提到 Jaxb 在标记为 @XmlAnyElement(lax = true) 时留下了它不知道如何映射为 DOM 元素的任何东西,我最终使用 org.w3c.dom 得到了一个 XmlAdapter。元素作为 Jaxb 知道如何相互映射的中间类。

    import java.io.ByteArrayInputStream;
    import java.nio.charset.StandardCharsets;
    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    import javax.xml.transform.stream.StreamSource;
    
    import org.jdom.Element;
    import org.jdom.output.DOMOutputter;
    import org.jdom.output.XMLOutputter;
    import org.w3c.dom.Document;
    
    public class Test {
    
      public static void main(String[] args) throws JAXBException {
    
        String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
        JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
                .createContext(new Class<?>[] {Foo.class}, null);
        javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Foo foo = unmarshaller
                .unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
                .getValue();
    
        System.out.println(foo);
      }
    
      @XmlRootElement
      private static final class Foo {
    
        @XmlAttribute
        public String bar;
    
        @XmlElement
        @XmlJavaTypeAdapter(ElementAdapter.class)
        public Element baz;
    
        @Override
        public String toString() {
            return "Foo [bar=" + bar + ", baz=" + jdomElementToString(baz) + "]";
        }
    
        private String jdomElementToString(Element element) {
            return new XMLOutputter(org.jdom.output.Format.getPrettyFormat()).outputString(element);
        }
    
      }
    
      private static final class ElementAdapter extends XmlAdapter<org.w3c.dom.Element, Element> {
    
        @Override
        public Element unmarshal(org.w3c.dom.Element valueToUnmarshal) throws Exception {
            org.jdom.input.DOMBuilder domBuilder = new org.jdom.input.DOMBuilder();
            org.jdom.Element jdomElement = domBuilder.build(valueToUnmarshal);
            return jdomElement;
        }
    
        @Override
        public org.w3c.dom.Element marshal(Element elementToMarshal) throws Exception {
            org.jdom.Document jdomDocument = new org.jdom.Document((Element) elementToMarshal.detach());
            DOMOutputter domOutputter = new DOMOutputter();
            Document domDocument = domOutputter.output(jdomDocument);
            return domDocument.getDocumentElement();
        }
    
      }
    }
    

    运行的打印结果是:

    Foo [bar=bar, baz=<baz>
        <test value="something" />
    </baz>]
    

    正如您所见,baz 标记中的内容被“按原样”分配给 Foo 类中的变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-28
      • 1970-01-01
      • 1970-01-01
      • 2013-01-17
      • 1970-01-01
      • 2013-05-30
      • 1970-01-01
      相关资源
      最近更新 更多