Question

I've a service that returns this XML:

<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<result>
    <project>
        <id>id1</id>
            <owner>owner1</owner>
    </project>
    <project>
        <id>id2</id>
            <owner>owner2</owner>
    </project>
</result>

or

<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<result>
    <user>
        <id>id1</id>
        <name>name1</name>
    </user>
    <user>
        <id>id2</id>
            <name>name2</name>
    </user>
</result>

I want to unmarshall the retrieved XML using these classes:

Result:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {

  @XmlElement
  protected String status;

  @XmlElementWrapper(name = "result")
  @XmlElement
  protected List<T> result;
}

Project:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Project {

  @XmlElement
  public String id;

  @XmlElement
  public String owner;
}

User:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class User {

  @XmlElement
  public String id;

  @XmlElement
  public String name;
}

First not working solution

JAXBContext context = JAXBContext.newInstance(Response.class, Project.class, User.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

StreamSource source = new StreamSource(new File("responseProject.xml"));
Response<Project> responseProject = (Response<Project>)unmarshaller.unmarshal(source);
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);

source = new StreamSource(new File("responseUser.xml"));
Response<User> responseUser = (Response<User>)unmarshaller.unmarshal(source);
System.out.println(responseUser.getStatus());
for (User user:responseUser.getResult()) System.out.println(user);

I get an empty list.

Second not working solution

Inspired by this article http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html I've modified the Response class:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {

  @XmlElement
  protected String status;

  @XmlAnyElement(lax=true)
  protected List<T> result;
}

And then tested it with this code:

  Response<Project> responseProject = unmarshal(unmarshaller, Project.class, "responseProject.xml");
  System.out.println(responseProject.getStatus());
  for (Project project:responseProject.getResult()) System.out.println(project);

private static <T> Response<T> unmarshal(Unmarshaller unmarshaller, Class<T> clazz, String xmlLocation) throws JAXBException {
  StreamSource xml = new StreamSource(xmlLocation);
  @SuppressWarnings("unchecked")
  Response<T> wrapper = (Response<T>) unmarshaller.unmarshal(xml, Response.class).getValue();
  return wrapper;
}

And I get this exception reading the response list:

Exception in thread "main" java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to org.test.Project

Note: I can't modify the original XML. There are more types other than Project and User.

Was it helpful?

Solution

Thanks to Blaise Doughan and his article I've found the solution.

First we need the Wrapper class provided in the article:

@XmlRootElement
public class Wrapper<T> {

  private List<T> items;

  public Wrapper() {
    items = new ArrayList<T>();
  }

  public Wrapper(List<T> items) {
    this.items = items;
  }

  @XmlAnyElement(lax=true)
  public List<T> getItems() {
    return items;
  }
}

Then I've modified the Response class in order to use it:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Response<T> {

  @XmlElement
  protected String status;

  @XmlElement
  protected Wrapper<T> result;

  ...

  public Response(String status, List<T> result) {
    this.status = status;
    this.result = new Wrapper<>(result);
  }

  ...

  public List<T> getResult() {
    return result.getItems();
  }

  ...
}

Finally the unmarshalling code:

JAXBContext context = JAXBContext.newInstance(Response.class, Project.class, User.class, Wrapper.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

StreamSource source = new StreamSource(new File("responseProject.xml"));
Response<Project> responseProject = (Response<Project>)unmarshaller.unmarshal(source);
System.out.println(responseProject.getStatus());
for (Project project:responseProject.getResult()) System.out.println(project);

source = new StreamSource(new File("responseUser.xml"));
Response<User> responseUser = (Response<User>)unmarshaller.unmarshal(source);
System.out.println(responseUser.getStatus());
for (User user:responseUser.getResult()) System.out.println(user);

I've added the Wrapper class to the context class list.

Alternatively you can add this annotation to the Response class:

@XmlSeeAlso({Project.class, User.class})

OTHER TIPS

Using @XmlSeeAlso({Project.class, User.class}) on Response classes has the drawback of generating some garbage information on each entity in the list: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount"

<resources>
    <links>
        <link>
            <rel>self</rel>
            <uri>http://localhost:8080/salonea-1.0/rest/user-accounts?offset=0&amp;limit=2</uri>
        </link>
        <link>
            <rel>prev</rel>
            <uri></uri>
        </link>
        <link>
            <rel>next</rel>
            <uri>http://localhost:8080/salonea-1.0/rest/user-accounts?offset=2&amp;limit=2</uri>
        </link>
    </links>
    <collection>
        <user-account 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount">
            <accountType>user</accountType>
            <activationCode>638f502a0e409348ccc2e36c24907f0</activationCode>
            <email>michzio@hotmail.com</email>
            <login>michzio</login>
            <password>sAmPL3#e</password>
            <registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
            <userId>1</userId>
        </user-account>
        <user-account 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userAccount">
            <accountType>user</accountType>
            <activationCode>334bc79d142a291894bd71881e38a719</activationCode>
            <email>alicja@krainaczarow.com</email>
            <login>alicja</login>
            <password>zAczka!00</password>
            <registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
            <userId>2</userId>
        </user-account>
    </collection>
</resources>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top