简体   繁体   中英

How can JAXB unmarshal nested xml elements into a string?

I am trying to convert xml to my Java class

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

@XmlElement(name="UserName")
private String username;

@XmlElement(name="Password")
private String password;

@XmlElement(name="XMLPost")
private String xmlPost;

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public String getXmlPost() {
    return xmlPost;
}

public void setXmlPost(String xmlPost) {
    this.xmlPost = xmlPost;
}}

Here is my context

JAXBContext jc = JAXVContext.newInstance(Request.class, Report.class, Status.class);
StringReader reader = new StringReader(xml);
Unmarshaller unmarshaller = jc.createUnmarshaller();

And here is how I use them together

Request request = unmarshaller.unmarshal(reader);

In the Request class the xmlPost will contain either xml of Report or xml of Status So I would like to capture it into a string so that I can try to unmarshal each case.

The Report class comes from a 3rd party request that I have no control over. However when I attempt to unmarshal the Request if there is any XML in XMLPost field it tries to unmarshal it as well, even though I have specified that it should just be a String . Why can't I stuff the xml that in XMLPost in to a String ?

Thanks for all the help in advanced.

You can achieve using cusom handler to convert it, Please check this post. I hope it would help you to fix ur issue : http://blog.bdoughan.com/2011/04/xmlanyelement-and-non-dom-properties.html

You cannot do that directly, because it does not follow XML specification. When you define:

@XmlElement(name="XMLPost")
private String xmlPost;

it means that JAXB according to XML specification expects XML with simple type element XMLPost as xs:string. not as another XML element. To have a value in it your Request must look like:

<Request>
    <XMLPost>some string in it</XMLPost>
</Request>

So, if you'd like to have another XML in it as a String you must have it either in XML escaped form like:

<Request>
    <XMLPost>&lt;Report&gt; ...xml escaped content... &lt;/Report&gt;</XMLPost>
</Request>

or as CDATA element.

<Request>
    <XMLPost><![CDATA[<Report> ...xml content... </Report>]]></XMLPost>
</Request>

From other hands you can have two fields as your complex XML elements

@XmlElement(name="Report")
private Report report;

@XmlElement(name="Status")
private Status status;

In that case you have to have JAXB classes for Report and Status Then if your Request has Report element then report field will have it and status will be null or when Request has Status then status field will have it and report field will be null.

Maybe it is more appropriate approach if you need your Report and Status without taking a String and unmarshaling them separately. It is up to you and what you need to do with those elements...

In terms of XML Schema what you did:

<xs:element name="Request">
   <xs:complexType>
     <xs:sequence>
         <xs:element name="XMLPost" type="xs:string">
     </xs:sequence>
   </xs:complexType>     
 </xs:element>

for the second approach it will look like:

<xs:element name="Request">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="Report" minOccurs="0">
                <xs:complexType>
                    <xs:sequence>
                        <!-- ... Report type definition ... -->
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
            <xs:element name="Status" minOccurs="0">
                <xs:complexType>
                    <xs:sequence>
                        <!-- ... Status type definition ... -->
                    </xs:sequence>
                </xs:complexType>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

And finally if you need only one field to keep either Report or Status object (not String) define it as Object

@XmlElement(name="XMLPost")
private Object xmlPost;

It corresponds to XSD:

<element name="XMLPost" type="xs:anyType" />

Of course when you process your request it is up to you to check what is a Class of xmlPost object

What I would really do is create an XMLPost object, with optional report and status fields. Then after unmarshalling, you can see which one is populated.

However to answer your original question, you should use an XmlAdapter ( In JAXB, how to use @XmlJavaTypeAdapters annotation? ). Here is an example of what the adapter might look like:

public class XmlPostStringAdapter extends XmlAdapter<XMLPost, String> {

  @Override
  public String unmarshal(XMLPost xmlPost) throws Exception {
     StringWriter sw = new StringWriter();
     JAXBContext jaxbContext = JAXBContext.newInstance(XmlPost.class);
     Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

     jaxbMarshaller.marshal(xmlPost, sw);
     return sw.toString();
  }

  @Override
  public XMLPost marshal(String xmlPost) throws Exception {
     JAXBContext jaxbContext = JAXBContext.newInstance(XMLPost.class);
     Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
    return (XMLPost) jaxbUnmarshaller.unmarshal(new StringReader(xmlPost));
  }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM