In a RestFul-Webservice (Jersey) context I need to marshal / serialize an Object graph to XML and JSON. For simplicity I try to explain the problem with 2-3 classes:
Person.java
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
private String name;
// @XmlIDREF
@XmlElement(name = "house")
@XmlElementWrapper(name = "houses")
private Collection<House> houses;
public Person() {}
public Person(String name, Collection<House> houses) {
this.name = name;
this.houses = houses;
}
}
House.java
@XmlAccessorType(XmlAccessType.FIELD)
public class House {
// @XmlID
public String name;
public String location;
public House() {}
public House(String name, String location) {
this.name = name;
this.location = location;
}
}
Now when I serialize a Person, the XML will look like this:
<people>
<person>
<name>Edward</name>
<houses>
<house>
<name>MyAppartment</name>
<location>London</location>
</house>
<house>
<name>MySecondAppartment</name>
<location>London</location>
</house>
</houses>
</person>
<person>
<name>Thomas</name>
<houses>
<house>
<name>MyAppartment</name>
<location>London</location>
</house>
<house>
<name>MySecondAppartment</name>
<location>London</location>
</house>
</houses>
</person>
</people>
The problem here is, that the same houses are listed multiple times. Now I add the uncommented XmlIDREF
and XmlID
annotations, which would result in XML similar to this:
<people>
<person>
<name>Edward</name>
<houses>
<house>MyAppartment</house>
<house>MySecondAppartment</house>
</houses>
</person>
<person>
<name>Thomas</name>
<houses>
<house>MyAppartment</house>
<house>MySecondAppartment</house>
</houses>
</person>
</people>
While the first XML was too verbose, this one lacks of information. How can I create (and unmarshal) something similar to:
<people>
<person>
<name>Edward</name>
<houses>
<house>MyAppartment</house>
<house>MySecondAppartment</house>
</houses>
</person>
<person>
<name>Thomas</name>
<houses>
<house>MyAppartment</house>
<house>MySecondAppartment</house>
</houses>
</person>
<houses>
<house>
<name>MyAppartment</name>
<location>London</location>
</house>
<house>
<name>MySecondAppartment</name>
<location>London</location>
</house>
</houses>
</people>
The solution should be generic because I don't want to write extra classes for each new element in the object graph.
For completeness, here's the restful webservice:
@Path("rest/persons")
public class TestService {
@GET
@Produces({ MediaType.TEXT_XML, MediaType.APPLICATION_JSON })
public Collection<Person> test() throws Exception {
Collection<Person> persons = new ArrayList<Person>();
Collection<House> houses = new HashSet<House>();
houses.add(new House("MyAppartment", "London"));
houses.add(new House("MySecondAppartment", "London"));
persons.add(new Person("Thomas", houses));
persons.add(new Person("Edward", houses));
return persons;
}
}
Thanks in advance.
If you are trying to serialize into a format that matches the last XML example you gave then I believe your object graph is structured incorrectly to achieve that.
If you want to provide a collection of Person
objects with their associated houses and also provide a collection of the House
objects then you need to return a serialized XML message which contains both collections. It appears as though you have your @XmlIDREF
and @XmlID
annotations in the correct place for making the Person-House association as you intend (based on your description) but you only return a collection of the Person
objects as opposed to returning both collections.
Your webservice should look more like this (leaving out the serialization as it appears you're clear on how to serialize it):
@Path("rest/persons")
public class TestService {
@GET
@Produces({ MediaType.TEXT_XML, MediaType.APPLICATION_JSON })
public Map<String, Object> test() throws Exception {
Map<String, Object> peopleAndHouses = new HashMap<String, Object>();
Collection<Person> persons = new ArrayList<Person>();
Collection<House> houses = new HashSet<House>();
houses.add(new House("MyAppartment", "London"));
houses.add(new House("MySecondAppartment", "London"));
persons.add(new Person("Thomas", houses));
persons.add(new Person("Edward", houses));
peopleAndHouses.put("houses", houses);
peopleAndHouses.put("people", persons);
return peopleAndHouses;
}
}
There are other ways to accomplish this (e.g. create a wrapper object which has collection attributes for people and houses, etc.) but hopefully you get the idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With