I'm trying to create a very simple REST server. I just have a test method that will return a List of Strings. Here's the code:
#GET
#Path("/test2")
public List test2(){
List list=new Vector();
list.add("a");
list.add("b");
return list;
}
It gives the following error:
SEVERE: A message body writer for Java type,
class java.util.Vector, and MIME media type,
application/octet-stream, was not found
I was hoping JAXB had a default setting for simple types like String, Integer, etc. I guess not. Here's what I imagined:
<Strings>
<String>a</String>
<String>b</String>
</Strings>
What's the easiest way to make this method work?
I used #LiorH's example and expanded it to:
#XmlRootElement(name="List")
public class JaxbList<T>{
protected List<T> list;
public JaxbList(){}
public JaxbList(List<T> list){
this.list=list;
}
#XmlElement(name="Item")
public List<T> getList(){
return list;
}
}
Note, that it uses generics so you can use it with other classes than String. Now, the application code is simply:
#GET
#Path("/test2")
public JaxbList test2(){
List list=new Vector();
list.add("a");
list.add("b");
return new JaxbList(list);
}
Why doesn't this simple class exist in the JAXB package? Anyone see anything like it elsewhere?
#GET
#Path("/test2")
public Response test2(){
List<String> list=new Vector<String>();
list.add("a");
list.add("b");
final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
return Response.ok().entity(entity).build();
}
In case anyone of you wants to write a list wrapper for lists containing elements of multiple classes and want to give an individual XmlElement name according to the Class type without Writing X Wrapper classes you could use the #XmlMixed annotation.
By doing so JAXB names the items of the list according to the value set by the #XmlRootElement.
When doing so you have to specify which classes could possibly be in the list using #XmlSeeAlso
Example:
Possible Classes in the list
#XmlRootElement(name="user")
public class User {/*...*/}
#XmlRootElement(name="entry")
public class LogEntry {/*...*/}
Wrapper class
#XmlRootElement(name="records")
#XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
#XmlMixed
public List<T> getRecords(){
return records;
}
}
Example:
List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));
XStream xStream = new XStream();
String result = xStream.toXML(l);
Result:
<records>
<user>...</user>
<entry>...</entry>
</records>
Alternatevily you could specify the XmlElement names directly inside the wrapper class using the #XmlElementRef annotation
#XmlRootElement(name="records")
#XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
#XmlElementRefs({
#XmlElementRef(name="item", type=Object.class),
#XmlElementRef(name="user", type=User.class),
#XmlElementRef(name="entry", type=LogEntry.class)
})
public List<T> getRecords(){
return records;
}
}
From a personal blog post, it is not necessary to create a specific JaxbList < T > object.
Assuming an object with a list of strings:
#XmlRootElement
public class ObjectWithList {
private List<String> list;
#XmlElementWrapper(name="MyList")
#XmlElement
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
A JAXB round trip:
public static void simpleExample() throws JAXBException {
List<String> l = new ArrayList<String>();
l.add("Somewhere");
l.add("This and that");
l.add("Something");
// Object with list
ObjectWithList owl = new ObjectWithList();
owl.setList(l);
JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
ObjectWithList retr = marshallUnmarshall(owl, jc);
for (String s : retr.getList()) {
System.out.println(s);
} System.out.println(" ");
}
Produces the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
<MyList>
<list>Somewhere</list>
<list>This and that</list>
<list>Something</list>
</MyList>
</objectWithList>
This can be done MUCH easier using wonderful XStream library. No wrappers, no annotations.
Target XML
<Strings>
<String>a</String>
<String>b</String>
</Strings>
Serialization
(String alias can be avoided by using lowercase string tag, but I used OP's code)
List <String> list = new ArrayList <String>();
list.add("a");
list.add("b");
XStream xStream = new XStream();
xStream.alias("Strings", List.class);
xStream.alias("String", String.class);
String result = xStream.toXML(list);
Deserialization
Deserialization into ArrayList
XStream xStream = new XStream();
xStream.alias("Strings", ArrayList.class);
xStream.alias("String", String.class);
xStream.addImplicitArray(ArrayList.class, "elementData");
List <String> result = (List <String>)xStream.fromXML(file);
Deserialization into String[]
XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);
Note, that XStream instance is thread-safe and can be pre-configured, shrinking code amount to one-liners.
XStream can also be used as a default serialization mechanism for JAX-RS service. Example of plugging XStream in Jersey can be found here
I have encountered this pattern a few times, I found that the easiest way is to define an inner class with JaxB annotations. (anyways, you'll probably want to define the root tag name)
so your code would look something like this
#GET
#Path("/test2")
public Object test2(){
MyResourceWrapper wrapper = new MyResourceWrapper();
wrapper .add("a");
wrapper .add("b");
return wrapper ;
}
#XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
#XmlElement(name="Item")
List<String> list=new ArrayList<String>();
MyResourceWrapper (){}
public void add(String s){ list.add(s);}
}
if you work with javax.rs (jax-rs) I'd return Response object with the wrapper set as its entity
Finally I've solved it using JacksonJaxbJsonProvider It requires few changes in your Spring context.xml and Maven pom.xml
In your Spring context.xml add JacksonJaxbJsonProvider to the <jaxrs:server>:
<jaxrs:server id="restService" address="/resource">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
In your Maven pom.xml add:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.0</version>
</dependency>
User1's example worked well for me. But, as a warning, it won't work with anything other than simple String/Integer types, unless you add an #XmlSeeAlso annotation:
#XmlRootElement(name = "List")
#XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
protected List<MovieTicket> list;
This works OK, although it prevents me from using a single generic list class across my entire application. It might also explain why this seemingly obvious class doesn't exist in the JAXB package.
Make sure to add #XmlSeeAlso tag with your specific classes used inside JaxbList. It is very important else it throws HttpMessageNotWritableException
I would've saved time if I found Resteasy Jackson Provider sooner.
Just add the Resteasy Jackson Provider JAR. No entity wrappers. No XML annotations. No custom message body writers.
If you are using maven in the jersey project add below in pom.xml and update project dependencies so that Jaxb is able to detect model class and convert list to Media type application XML:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
For a more general solution, for JAXB-XML serialization of any top level list , which only requires 1 new class to be written, check out the solution given in this question:
Is it possible to programmatically configure JAXB?
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}
Related
I am trying to unmarshal the XML and map it to the Java POJO. My XML can have some of the user-defined elements which can be random so I would like to store them. After researching I found that I can use #XmlAnyElement(lax=true). I am trying to use the XMLAdapter along with the #XmlAnyElement but for some reason, the method unmarshal within my XMLAdapter is not being called at all due to which I am unable to map the user-defined fields.
Can someone please explain to me how to unmarshal the user-defined fields to my Map<String,Object> for the marshalling everything is working fine and I am using the approach mentioned here. But unmarshalling is not being called at all which is bit confusing for me.
Following is my XML which needs to be unmarshalled. (Please note that the namespace can be dynamic and user-defined which may change in every xml):
<Customer xmlns:google="https://google.com">
<name>Batman</name>
<google:main>
<google:sub>bye</google:sub>
</google:main>
</Customer>
Following is my Customer class to which XML needs to be unmarshalled;
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private String name;
#XmlAnyElement(lax = true)
#XmlJavaTypeAdapter(TestAdapter.class)
private Map<String, Object> others = new HashMap<>();
//Getter Setter and other constructors
}
Following is my XMLAdapter (TestAdapter) class which will be used for marshalling and unmarshalling the user-defined fields. The unmarshalling method is not being called at all. However the marshalling method works as expected based on the code provided here.
class TestAdapter extends XmlAdapter<Wrapper, Map<String,Object>> {
#Override
public Map<String,Object> unmarshal(Wrapper value) throws Exception {
//Method not being called at all the following SYSTEM.OUT is NOT PRINTED
System.out.println("INSIDE UNMARSHALLING METHOD TEST");
System.out.println(value.getElements());
return null;
}
#Override
public Wrapper marshal(Map<String,Object> v) throws Exception {
return null;
}
}
class Wrapper {
#XmlAnyElement
List elements;
}
I have used the package-info.java file and it has been filled with following contents:
#jakarta.xml.bind.annotation.XmlSchema(namespace = "http://google.com", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED)
package io.model.jaxb;
I researched a lot but could not find any answer which does something similar. Also, tried a lot of things but none worked. Hence, posting the same here and looking for some suggestion or workaround.
I have few doubts with regards to unmarshalling:
Why my XMLAdapter TestAdapter.class unmarshal method is not being called during the unmarshalling?
How can I unmarshal the XML fields which can appear randomly with namespaces?
Am I doing something wrong or is there something else I should do to read the namespaces and elements which appear dynamically?
*** FOLLOWING IS EDITED SECTION BASED ON RESPONSE FROM Thomas Fritsch ****
Based on the response I have edited my class but still not working as expected:
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
private String name;
#JsonIgnore
#XmlJavaTypeAdapter(TestAdapter.class)
private List<Object> others;
#XmlTransient
#XmlAnyElement(lax = true)
private List<Element> another = new ArrayList<>();
}
So what's happening is that if I use #XmlTransient then the field another will not be populated during the unmarshalling and I need to keep it #XmlTransient because I do not want it during the marshalling similarly I have made #JsonIgnore for Map<String, Object> others because I do not need it during the unmarshalling but both things are conflicting with each other and not able to obtain the the required output.
My main goal is to convert the XML file to JSON and vice versa. For the above mentioned XML file I would like to obtain the following output in JSON:
{
"Customer": {
"name": "BATMAN",
"google:main": {
"google:sub": "bye"
}
}
}
Similarly when I convert this JSON then I should get the original XML.
Following is my Main.class:
class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException, JsonProcessingException {
//XML to JSON
JAXBContext jaxbContext = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("Customer.xml");
final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
final XMLStreamReader streamReader = xmlInputFactory.createXMLStreamReader(inputStream);
final Customer customer = unmarshaller.unmarshal(streamReader, Customer.class).getValue();
final ObjectMapper objectMapper = new ObjectMapper();
final String jsonEvent = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
System.out.println(jsonEvent);
//JSON to XML
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(customer, System.out);
}
}
The Map type of your others property is not suitable for #XmlAnyElement.
According to the its javadoc the #XmlAnyElement annotation
is meant to be used with a List or an array property
(typically with a List or array of org.w3c.dom.Element).
May be you have confused this with the #XmlAnyAttribute annotation
which indeed is used with a Map property.
Hence in your Customer class the others property without using an adapter would look like this:
name;
#XmlAnyElement(lax = true)
private List<Element> others = new ArrayList<>();
And the others property with using an adapter should look like this:
#XmlAnyElement(lax = true)
#XmlJavaTypeAdapter(TestAdapter.class)
private List<MyObject> others = new ArrayList<>();
When doing this way, then JAXB will actually call your adapter.
The adapter's job is to transform between Element and MyObject.
public class TestAdapter extends XmlAdapter<Element, MyObject> {
#Override
public MyObject unmarshal(Element v) throws Exception {
...
}
#Override
public Element marshal(MyObject v) throws Exception {
...
}
}
After trying out a lot of things, I was able to get it working. Posting the solution for the same so it can be helpful to someone in the future.
I have used Project Lombok so you can see some additional annotations such as #Getter, #Setter, etc
Method-1:
As mentioned #Thomas Fritsch you have to use the #XmlAnyElement(lax=true) with List<Element> I was using with the Map<String, Object>.
Method-2:
You can continue to use Map<String,Object> and use #XmlPath(".") with adapter to get it working. Posting the code for the same here: (I have added one additional field age other that everything remain the same)
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "age", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Customer {
#XmlElement(name = "name")
private String name;
private String age;
#XmlJavaTypeAdapter(TestAdapter.class)
#XmlPath(".")
private Map<String, Object> others;
}
Following is the TestAdapter.class posting the same for reference. When you unmarhsal the method unmarshal in TestAdapter will get called and you can do anything you need.
class TestAdapter extends XmlAdapter<Wrapper, Map<String, Object>> {
#Override
public Map<String, Object> unmarshal(Wrapper value) throws Exception {
System.out.println("INSIDE UNMARSHALLING METHOD TEST");
final Map<String, Object> others = new HashMap<>();
for (Object obj : value.getElements()) {
final Element element = (Element) obj;
final NodeList children = element.getChildNodes();
//Check if its direct String value field or complex
if (children.getLength() == 1) {
others.put(element.getNodeName(), element.getTextContent());
} else {
List<Object> child = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
final Node n = children.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE) {
Wrapper wrapper = new Wrapper();
List childElements = new ArrayList();
childElements.add(n);
wrapper.elements = childElements;
child.add(unmarshal(wrapper));
}
}
others.put(element.getNodeName(), child);
}
}
return others;
}
#Override
public Wrapper marshal(Map<String, Object> v) throws Exception {
Wrapper wrapper = new Wrapper();
List elements = new ArrayList();
for (Map.Entry<String, Object> property : v.entrySet()) {
if (property.getValue() instanceof Map) {
elements.add(new JAXBElement<Wrapper>(new QName(property.getKey()), Wrapper.class, marshal((Map) property.getValue())));
} else {
elements.add(new JAXBElement<String>(new QName(property.getKey()), String.class, property.getValue().toString()));
}
}
wrapper.elements = elements;
return wrapper;
}
}
#Getter
class Wrapper {
#XmlAnyElement
List elements;
}
Although it works for specific cases, I am seeing one problem by using this approach. I have created a new post for this issue.
If I get any response for that issue then I will try to update the code so it can work correctly.
There are several previous questions around using JaxB to marshall/unmarshall a java.util.Map, many of which get pointed back to this example, which works great:
http://blog.bdoughan.com/2013/03/jaxb-and-javautilmap.html
However, I can't get JaxB to be able to marshall/unmarshall instances of Map if the map is not a member of the #XmlRootElement. For example, here's a root element class,
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public static class Customer {
private MyField myField
MyField getMyField() {
return myField
}
void setMyField(MyField myField) {
this.myField = myField
}
}
The definition of it's field class:
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{
Map<String, String> getSomeMap() {
return someMap
}
void setSomeMap(Map<String, String> someMap) {
this.someMap = someMap
}
#XmlElement
private Map<String, String> someMap = new HashMap<String, String>()
}
And some code to drive the marshalling:
JAXBContext jc = JAXBContext.newInstance(Customer.class)
Customer customer = new Customer()
MyField myField1 = new MyField()
myField1.someMap.put("foo", "bar")
myField1.someMap.put("baz", "qux")
customer.myField = myField1
Marshaller marshaller = jc.createMarshaller()
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
marshaller.marshal(customer, System.out)
This example results in:
java.util.Map is an interface, and JAXB can't handle interfaces.
java.util.Map does not have a no-arg default constructor.
I am writing my code in Groovy rather than Java, but I don't think it should make much of a difference.
I was able to encounter the same behavior using JAXB by creating a TestController of type #RestController, using Spring Boot.
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
#RestController
#RequestMapping(value = "test")
class TestController {
#RequestMapping(value = "findList")
List findList() {
["Test1", "Test2", "Test3"] as ArrayList<String>
}
#RequestMapping(value = "findMap")
Map findMap() {
["T1":"Test1", "T2":"Test2", "T3":"Test3"] as HashMap<String,String>
}
#RequestMapping(value = "")
String find(){
"Test Something"
}
}
With JAXB as the default implementation in SpringBoot, I could reproduce the issue that the /test/findList would correctly render XML, but /test/findMap would generate an error as described in the initial posting.
For me, the solution to the problem is to switch the XML rendering library to Jackson (there are others like XStream as well).
Using Gradle for the build file (build.gradle), I simply add the Jackson dependencies, very similar to how you would if using Maven:
'com.fasterxml.jackson.core:jackson-core:2.7.1',
'com.fasterxml.jackson.core:jackson-annotations:2.7.1',
'com.fasterxml.jackson.core:jackson-databind:2.7.1-1',
'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.7.1',
'org.codehaus.woodstox:woodstox-core-asl:4.4.1',
I have experienced this before myself. Bottom line is that the warning is telling you exactly the problem. You have defined your field as type java.util.Map. JAXB does not support interfaces. To correct your problem, you need to change the declaration of your field to a concrete Map type like:
private HashMap<String, String> someMap = new HashMap<String, String>()
Your other option is described in the link you referenced. You need to have a
MapAdapter class as referenced in the link you provided and then include that in the annotation, hinting to JAXB how it should marshal/unmarshal the Map type.
I think this link gives a clearer example of how to create and implement the MapAdapter:
JAXB: how to marshall map into <key>value</key>
The answer to the specific issue I was having ended up being removing the #XmlElement annotation from the Map field like so:
#XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{
Map<String, String> getSomeMap() {
return someMap
}
void setSomeMap(Map<String, String> someMap) {
this.someMap = someMap
}
//#XmlElement Remove this annotation
private Map<String, String> someMap = new HashMap<String, String>()
}
Without that annotation, the marshalling/unmarshalling works fine, and still interprets the Map as an XmlElement - there seems to be a bug with that annotation specifically. However, as #dlcole points out, an alternative (that would allow you to have more control over the format of your serialized representation) is to use Jackson rather than JAXB.
Lets say I have a list of objects like this: LinkedList<JsonAssessment> jsonAssessments....
It is returned to this kind of method:
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody
List<JsonAssessment> doSomething(....) {
.....
}
I am making AJAX call to this controller everything is working correctly as expected but I don't like the naming my JSON is returned. In firebug I am seeing:
{"LinkedList":[{"assessmentName":"........
The question is how can I rename that root element LinkedList? Is there any config I have to set?
EDIT
I do not want to use any wrapper objects.
EDIT
My ObjectMapper:
public class JsonObjectMapper extends ObjectMapper {
public JsonObjectMapper() {
super();
this.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
this.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
Spring: 3.2.4.RELEASE
Jackson: 2.1.2
EDIT
This object mapper is declared in MVC message converters:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jsonObjectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
And that is what I've tried with naming strategy:
public class JsonPropertyNamingStrategy extends PropertyNamingStrategy {
public static final MyNamingStrategy MY_NAMING_STRATEGY = new MyNamingStrategy();
private static final LinkedHashMap<String, String> PROPERTIES = new LinkedHashMap<String, String>() {{
put("LinkedList", "resultList");
}};
public static class MyNamingStrategy extends PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
if (propertyName == null) {
return null;
}
if (PROPERTIES.containsKey(propertyName)) {
return PROPERTIES.get(propertyName);
}
return propertyName;
}
}
}
I was debugging it and when it comes to translate method property names comes all except the root element. I have everything that LinkedList contains, but LinkedList is not coming to this method.
Instead of directly returning a the List, wrap it inside a ResponseEntity that will give you a response without a root element
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody ResponseEntity<List<JsonAssessment>> doSomething(....) {
.....
return new ResponseEntity(yourList);
}
That way you don't have a root element. If you still want a root element you could add it to a Map. Results in the following JSON "[{"assessmentName":"........]
#RequestMapping(value = "mapping", method = RequestMethod.POST)
public
#ResponseBody ResponseEntity<Map<String, List<JsonAssessment>>> doSomething(....) {
.....
Map results = new HashMap();
result.put("assesments", yourlist);
return new ResponseEntity(results);
}
Should output {"assesments":[{"assessmentName":"........
Although you are stilling wrapping the objects here, it is in objects that are freely available, you don't have to add your own custom classes.
This is what we are using in a couple of our #Controllers, we are using Spring 3.2 and Jackson 2.2.
put the list in a wrapper class is a a commonly implemented strategy
If you are using Jackson Mapper you can use Annotations to define names of properties in classes by
#JsonProperty("foo")
or set the order by
#JsonPropertyOrder({"foo", "bar"})
and so on.
See further Jackson Annotations
edit:
Sorry, just saw the wrapper-comment. The only solution i saw is using a wrapper like this: How to rename root key in JSON serialization with Jackson
Say I have two JavaBeans Person and Address.
If I create a list of Person objects, I'd like to marshal to something like this:
<persons>
<person>...</person>
</persons>
It's possible to use the technique described here:
Using JAXB to unmarshal/marshal a List<String>
By annotating JaxbList with #XmlRootElement(name = "persons") and #XmlElement(name = "person"), then it's possible to marshal to the XML above.
But, it'd be nice to be able to reuse the same JaxbList<T> class to also marshal a list of Address objects. And in reality, I will have many other types of beans. I can go with something like:
<list>
<item xsi:type="person" xmlns:xsi="http://www.w2.org/2001/XmlSchema-instance"></item>
</list>
But, ideally, it'd be nice to have it replace "list" with the plural version of class name and "item" with the class name.
So, is it possible to programmatically configure the JaxbContext or something during runtime and essentially set the value of the name inside #XmlRootElement and #XmlElement?
Or any other way to get this working without having to write a separate implementation of JaxbList for every bean type? Maybe XmlJavaTypeAdapter can achieve this sort of thing?
Update
#Blaise Doughan's solution accepted below works great. For my use case, I needed to go straight from Java object to XML, here's what worked (note this is not my full implementation, it's sort of just pseudo code for demonstration):
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}
You could create a generic Wrapper object like the following:
Wrapper
You could create a generic wrapper class with a List property annotated with #XmlAnyElement(lax=true). The type of the object used to populate this list will be based on its root element (see: http://blog.bdoughan.com/2010/08/using-xmlanyelement-to-build-generic.html).
package forum13272288;
import java.util.*;
import javax.xml.bind.annotation.XmlAnyElement;
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
Address
You will need to annotate the possible contents of the list with #XmlRootElement.
package forum13272288;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Address {
}
Person
package forum13272288;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Person {
}
Demo
The demo code below demonstrates how to use the Wrapper class. Since the root element can be different you will need to specify that you want to unmarshal to the wrapper class. Alternatively you could leverage the #XmlElementDecl annotation to associate multiple root elements with the wrapper class (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html).
package forum13272288;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, Person.class, Address.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StreamSource personsXML = new StreamSource("src/forum13272288/persons.xml");
JAXBElement<Wrapper> wrapper1 = unmarshaller.unmarshal(personsXML, Wrapper.class);
marshaller.marshal(wrapper1, System.out);
StreamSource addressesXML = new StreamSource("src/forum13272288/addresses.xml");
JAXBElement<Wrapper> wrapper2 = unmarshaller.unmarshal(addressesXML, Wrapper.class);
marshaller.marshal(wrapper2, System.out);
}
}
Output
Below is the output from running the demo code. The files persons.xml and addresses.xml look just like there corresponding output.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persons>
<person/>
<person/>
</persons>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addresses>
<address/>
<address/>
</addresses>
For More Information
http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html
I'm trying to create a very simple REST server. I just have a test method that will return a List of Strings. Here's the code:
#GET
#Path("/test2")
public List test2(){
List list=new Vector();
list.add("a");
list.add("b");
return list;
}
It gives the following error:
SEVERE: A message body writer for Java type,
class java.util.Vector, and MIME media type,
application/octet-stream, was not found
I was hoping JAXB had a default setting for simple types like String, Integer, etc. I guess not. Here's what I imagined:
<Strings>
<String>a</String>
<String>b</String>
</Strings>
What's the easiest way to make this method work?
I used #LiorH's example and expanded it to:
#XmlRootElement(name="List")
public class JaxbList<T>{
protected List<T> list;
public JaxbList(){}
public JaxbList(List<T> list){
this.list=list;
}
#XmlElement(name="Item")
public List<T> getList(){
return list;
}
}
Note, that it uses generics so you can use it with other classes than String. Now, the application code is simply:
#GET
#Path("/test2")
public JaxbList test2(){
List list=new Vector();
list.add("a");
list.add("b");
return new JaxbList(list);
}
Why doesn't this simple class exist in the JAXB package? Anyone see anything like it elsewhere?
#GET
#Path("/test2")
public Response test2(){
List<String> list=new Vector<String>();
list.add("a");
list.add("b");
final GenericEntity<List<String>> entity = new GenericEntity<List<String>>(list) { };
return Response.ok().entity(entity).build();
}
In case anyone of you wants to write a list wrapper for lists containing elements of multiple classes and want to give an individual XmlElement name according to the Class type without Writing X Wrapper classes you could use the #XmlMixed annotation.
By doing so JAXB names the items of the list according to the value set by the #XmlRootElement.
When doing so you have to specify which classes could possibly be in the list using #XmlSeeAlso
Example:
Possible Classes in the list
#XmlRootElement(name="user")
public class User {/*...*/}
#XmlRootElement(name="entry")
public class LogEntry {/*...*/}
Wrapper class
#XmlRootElement(name="records")
#XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
#XmlMixed
public List<T> getRecords(){
return records;
}
}
Example:
List l = new List();
l.add(new User("userA"));
l.add(new LogEntry(new UserB()));
XStream xStream = new XStream();
String result = xStream.toXML(l);
Result:
<records>
<user>...</user>
<entry>...</entry>
</records>
Alternatevily you could specify the XmlElement names directly inside the wrapper class using the #XmlElementRef annotation
#XmlRootElement(name="records")
#XmlSeeAlso({User.class, LogEntry.class})
public static class JaxbList<T>{
protected List<T> records;
public JaxbList(){}
public JaxbList(List<T> list){
this.records=list;
}
#XmlElementRefs({
#XmlElementRef(name="item", type=Object.class),
#XmlElementRef(name="user", type=User.class),
#XmlElementRef(name="entry", type=LogEntry.class)
})
public List<T> getRecords(){
return records;
}
}
From a personal blog post, it is not necessary to create a specific JaxbList < T > object.
Assuming an object with a list of strings:
#XmlRootElement
public class ObjectWithList {
private List<String> list;
#XmlElementWrapper(name="MyList")
#XmlElement
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
A JAXB round trip:
public static void simpleExample() throws JAXBException {
List<String> l = new ArrayList<String>();
l.add("Somewhere");
l.add("This and that");
l.add("Something");
// Object with list
ObjectWithList owl = new ObjectWithList();
owl.setList(l);
JAXBContext jc = JAXBContext.newInstance(ObjectWithList.class);
ObjectWithList retr = marshallUnmarshall(owl, jc);
for (String s : retr.getList()) {
System.out.println(s);
} System.out.println(" ");
}
Produces the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWithList>
<MyList>
<list>Somewhere</list>
<list>This and that</list>
<list>Something</list>
</MyList>
</objectWithList>
This can be done MUCH easier using wonderful XStream library. No wrappers, no annotations.
Target XML
<Strings>
<String>a</String>
<String>b</String>
</Strings>
Serialization
(String alias can be avoided by using lowercase string tag, but I used OP's code)
List <String> list = new ArrayList <String>();
list.add("a");
list.add("b");
XStream xStream = new XStream();
xStream.alias("Strings", List.class);
xStream.alias("String", String.class);
String result = xStream.toXML(list);
Deserialization
Deserialization into ArrayList
XStream xStream = new XStream();
xStream.alias("Strings", ArrayList.class);
xStream.alias("String", String.class);
xStream.addImplicitArray(ArrayList.class, "elementData");
List <String> result = (List <String>)xStream.fromXML(file);
Deserialization into String[]
XStream xStream = new XStream();
xStream.alias("Strings", String[].class);
xStream.alias("String", String.class);
String[] result = (String[])xStream.fromXML(file);
Note, that XStream instance is thread-safe and can be pre-configured, shrinking code amount to one-liners.
XStream can also be used as a default serialization mechanism for JAX-RS service. Example of plugging XStream in Jersey can be found here
I have encountered this pattern a few times, I found that the easiest way is to define an inner class with JaxB annotations. (anyways, you'll probably want to define the root tag name)
so your code would look something like this
#GET
#Path("/test2")
public Object test2(){
MyResourceWrapper wrapper = new MyResourceWrapper();
wrapper .add("a");
wrapper .add("b");
return wrapper ;
}
#XmlRootElement(name="MyResource")
private static class MyResourceWrapper {
#XmlElement(name="Item")
List<String> list=new ArrayList<String>();
MyResourceWrapper (){}
public void add(String s){ list.add(s);}
}
if you work with javax.rs (jax-rs) I'd return Response object with the wrapper set as its entity
Finally I've solved it using JacksonJaxbJsonProvider It requires few changes in your Spring context.xml and Maven pom.xml
In your Spring context.xml add JacksonJaxbJsonProvider to the <jaxrs:server>:
<jaxrs:server id="restService" address="/resource">
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
In your Maven pom.xml add:
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.0</version>
</dependency>
User1's example worked well for me. But, as a warning, it won't work with anything other than simple String/Integer types, unless you add an #XmlSeeAlso annotation:
#XmlRootElement(name = "List")
#XmlSeeAlso(MovieTicket.class)
public class MovieTicketList {
protected List<MovieTicket> list;
This works OK, although it prevents me from using a single generic list class across my entire application. It might also explain why this seemingly obvious class doesn't exist in the JAXB package.
Make sure to add #XmlSeeAlso tag with your specific classes used inside JaxbList. It is very important else it throws HttpMessageNotWritableException
I would've saved time if I found Resteasy Jackson Provider sooner.
Just add the Resteasy Jackson Provider JAR. No entity wrappers. No XML annotations. No custom message body writers.
If you are using maven in the jersey project add below in pom.xml and update project dependencies so that Jaxb is able to detect model class and convert list to Media type application XML:
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
For a more general solution, for JAXB-XML serialization of any top level list , which only requires 1 new class to be written, check out the solution given in this question:
Is it possible to programmatically configure JAXB?
public class Wrapper<T> {
private List<T> items = new ArrayList<T>();
#XmlAnyElement(lax=true)
public List<T> getItems() {
return items;
}
}
//JAXBContext is thread safe and so create it in constructor or
//setter or wherever:
...
JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
...
public String marshal(List<T> things, Class clazz) {
//configure JAXB and marshaller
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
//Create wrapper based on generic list of objects
Wrapper<T> wrapper = new Wrapper<T>(things);
JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);
StringWriter result = new StringWriter();
//marshal!
m.marshal(wrapperJAXBElement, result);
return result.toString();
}