'Unmarshalling an XML using Xpath expression and jaxb

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



Solution 1:[1]

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

Solution 2:[2]

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);
    }

}

Solution 3:[3]

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

Solution 4:[4]

@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!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pyves
Solution 2 bdoughan
Solution 3 bdoughan
Solution 4 user19873