Unmarshalling generic list with JAXB
Solution 1
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})
Solution 2
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&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&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>[email protected]</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>[email protected]</email>
<login>alicja</login>
<password>zAczka!00</password>
<registrationDate>2015-09-03T17:30:03+02:00</registrationDate>
<userId>2</userId>
</user-account>
</collection>
</resources>
Comments
-
Fedy2 almost 2 years
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.