19

I am new to JAXB and would like to know if there is a way by which I can unmarshall an XML to my response object but using xpath expressions. The issue is that I am calling a third party webservice and the response which I receive has a lot of details. I do not wish to map all the details in the XML to my response object. I just wish to map few details from the xml using which I can get using specific XPath expressions and map those to my response object. Is there an annotation which can help me achieve this?

For example consider the following response

<root>
  <record>
    <id>1</id>
    <name>Ian</name>
    <AddressDetails>
      <street> M G Road </street>
    </AddressDetails>
  </record>  
</root>

I am only intrested in retrieving the street name so I want to use xpath expression to get value of street using 'root/record/AddressDetails/street' and map it to my response object

public class Response{
     // How do i map this in jaxb, I do not wish to map record,id or name elements
     String street; 

     //getter and setters
     ....
}   

Thanks

David J. Liszewski
  • 10,959
  • 6
  • 44
  • 57
Pratik Shelar
  • 3,154
  • 7
  • 31
  • 51

4 Answers4

22

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

You could use MOXy's @XmlPath extension for this use case.

Response

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

@XmlRootElement(name="root")
@XmlAccessorType(XmlAccessType.FIELD)
public class Response{
    @XmlPath("record/AddressDetails/street/text()")
    String street; 

    //getter and setters
}

jaxb.properties

To use MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html)

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

Demo

import java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Response.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum17141154/input.xml");
        Response response = (Response) unmarshaller.unmarshal(xml);

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

}

Output

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <record>
      <AddressDetails>
         <street> M G Road </street>
      </AddressDetails>
   </record>
</root>

For More Information

Pyves
  • 6,333
  • 7
  • 41
  • 59
bdoughan
  • 147,609
  • 23
  • 300
  • 400
11

If all you want is the street name, just use an XPath expression to get it as a string, and forget about JAXB - the complex JAXB machinery is not adding any value.

import javax.xml.xpath.*;
import org.xml.sax.InputSource;

public class XPathDemo {

    public static void main(String[] args) throws Exception {
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xpath = xpf.newXPath();

        InputSource xml = new InputSource("src/forum17141154/input.xml");
        String result = (String) xpath.evaluate("/root/record/AddressDetails/street", xml, XPathConstants.STRING);
        System.out.println(result);
    }

}
bdoughan
  • 147,609
  • 23
  • 300
  • 400
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • @Blaise I get java.net.MalformedURLException: no protocol: error when I try with the above code. Just above code with above xml. – user2771655 Feb 05 '14 at 09:47
  • The spec for "new InputSource()" says that the supplied SystemId must be an absolute URI, not a relative URI as supplied in this example. – Michael Kay Feb 05 '14 at 11:58
0

If you want to map to the middle of an XML document you could do the following:

Demo Code

You could use a StAX parser to advance to the part of the document you wish to unmarshal, and then unmarshal the XMLStreamReader from there,.

import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Response.class);

        XMLInputFactory xif = XMLInputFactory.newFactory();
        StreamSource xml = new StreamSource("src/forum17141154/input.xml");
        XMLStreamReader xsr = xif.createXMLStreamReader(xml);
        while(!(xsr.isStartElement() && xsr.getLocalName().equals("AddressDetails"))) {
            xsr.next();
        }

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<Response> response = unmarshaller.unmarshal(xsr, Response.class);

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

}

Response

The mappings on the domain object are made relative to the fragment of the XML document you are mapping to.

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Response{

    String street; 

    //getter and setters
}

Output

<?xml version="1.0" encoding="UTF-8"?>
<AddressDetails>
   <street> M G Road </street>
</AddressDetails>

For More Information

bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Is it required to create a separate jaxb domain object or can I reuse the ones created via xjc from the complete xsd (especially when extracing a larger part of the complete document tree)? – MRalwasser Apr 02 '14 at 15:38
  • @MRalwasser - You can use the ones generated from the XML Schema. – bdoughan Apr 02 '14 at 15:41
  • thanks, this is what I'm currently doing - do you have an idea why my unmarshaller creates an instance of the desired model but doesn't map any child elements into it? – MRalwasser Apr 02 '14 at 15:45
  • @MRalwasser - If you copy the part of the document you want to unmarshal into its own XML document (fixing up namespace declarations as necessary) does that unmarshal correctly. This will help confirm if your mappings are correct. – bdoughan Apr 02 '14 at 15:51
  • I did that - it seems that the top-level element of the xml part is an "unexpected" element. Maybe the reason is that it's neither a root element nor defined as an @XmlElementDecl annotated create...() method in the ObjectFactory. What are my options? – MRalwasser Apr 02 '14 at 16:07
  • You should use one of the `unmarshal` methods that take a class parameter. This tells JAXB which object you want to unmarshal and you don't need root element information for it. – bdoughan Apr 02 '14 at 16:09
  • Did that - this worked perfectly, including the child elements - which brings me back to my original question in my first comment why the partly unmarshalling returns an empty model. – MRalwasser Apr 02 '14 at 16:18
  • @MRalwasser - Is the `XMLStreamReader` at the local root element that you think it is when you navigate to the middle of the XML document? – bdoughan Apr 02 '14 at 16:25
  • Yes, it's at the correct element (but it's not a XmlRootElement as stated above).I'm not sure if this is important: the xml (string) which I'm trying to unmarshal partly is itself a part only. So in contrast to the original question here, I'd like to unmarshal a part of a JAXB model tree out of a xml-part (string) (both match exactly). Is this possible - or even my problem here? – MRalwasser Apr 02 '14 at 16:36
  • @MRalwasser - Is it possible for you to start a new question on Stack Overflow for this? – bdoughan Apr 02 '14 at 16:37
  • Done: http://stackoverflow.com/questions/22818252/jaxb-partial-unmarshal-returns-empty-domain-object – MRalwasser Apr 02 '14 at 17:03
0

@Xpath of Moxy works like a charm. But I got only null values in the beginning. Seems that it works a bit different with java 11 and above:

For me this post was helpful: @XmlPath has no impact during the JAXB Marshalling .

  1. It finally worked with using these dependencies:

    <dependency>
         <groupId>org.eclipse.persistence</groupId>
         <artifactId>eclipselink</artifactId>
         <version>3.0.2</version>
     </dependency>
    
     <dependency>
         <groupId>org.eclipse.persistence</groupId>
         <artifactId>org.eclipse.persistence.moxy</artifactId>
         <version>3.0.2</version>
     </dependency>
    
     <dependency>
         <groupId>org.glassfish.jaxb</groupId>
         <artifactId>jaxb-runtime</artifactId>
         <version>3.0.2</version>
     </dependency>
    
     <dependency>
         <groupId>jakarta.activation</groupId>
         <artifactId>jakarta.activation-api</artifactId>
         <version>2.0.1</version>
     </dependency>
    
  2. This in de jaxb.properties file (notice 'jakarta', this is different for older versions of java: jakarta.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

  3. Put this jaxb.properties file in the exact same location as the class files

  4. In my pom.xml also the underneath, to copy de jaxb.properties file to the exact same location. (check in you target folder if it worked, the properties file should be in the exact same folder.

    org.apache.maven.plugins maven-compiler-plugin src/main/java **/*.java
  5. Check in your imports that for jakarta is used for the Unmarshaller etc. So your import statements should be like this:

import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.Unmarshaller;

Good luck!

Rhode
  • 21
  • 4