Recently I've been asked in a couple different forums how to handle case insensitive unmarshalling in JAXB. In this post I'll discuss an approach using a StAX StreamReaderDelegate.
I am considering adding this as an official feature, and would be interested in your feed back. I am using the following bug to track this issue:
I am considering adding this as an official feature, and would be interested in your feed back. I am using the following bug to track this issue:
XML Inputs
We want our JAXB implementation to be able to unmarshal the following XML documents to the same object model:
<customer id="1"> <name>Jane Doe</name> <address> <street>123 A Street</street> </address> </customer>
<CUSTOMER ID="1"> <NAME>Jane Doe</NAME> <ADDRESS> <STREET>123 A Street</STREET> </ADDRESS> </CUSTOMER>
<CuStOMeR Id="1"> <NaMe>Jane Doe</NaMe> <AdDrEsS> <StReEt>123 A Street</StReEt> </AdDrEsS> </CuStOMeR>
Java Model
In the object model we will ensure that all the resulting node names are lower case. For this object model the default mappings give us this behavior. For other object models we could leverage the @XmlElement and @XmlAttribute annotations to achieve this:
package blog.caseinsensitive; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Customer { private int id; private String name; private Address address; @XmlAttribute public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
package blog.caseinsensitive; public class Address { private String street; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
Demo Code
We will achieve the desired effect by filtering the XML input. This will be done by using a StreamReaderDelegate. This delegate lets us filter the output from an XMLStreamReader. We will leverage this delegate to return all element and attribute names as lower case.
package blog.caseinsensitive; import java.io.FileInputStream; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.util.StreamReaderDelegate; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); XMLInputFactory xif = XMLInputFactory.newInstance(); XMLStreamReader xsr = xif.createXMLStreamReader(new FileInputStream("src/blog/caseinsensitive/input2.xml")); xsr = new MyStreamReaderDelegate(xsr); Unmarshaller unmarshaller = jc.createUnmarshaller(); Customer customer = (Customer) unmarshaller.unmarshal(xsr); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } private static class MyStreamReaderDelegate extends StreamReaderDelegate { public MyStreamReaderDelegate(XMLStreamReader xsr) { super(xsr); } @Override public String getAttributeLocalName(int index) { return super.getAttributeLocalName(index).toLowerCase(); } @Override public String getLocalName() { return super.getLocalName().toLowerCase(); } } }
XML Output
The following is the resulting XML, the output will match the provided JAXB mappings:
<customer id="1"> <name>Jane Doe</name> <address> <street>123 A Street</street> </address> </customer>
Do you know how I could go about unmarshalling a list?
ReplyDeleteHi Sohail,
ReplyDeleteCheck out the following post:
- JAXB & Collection Properties
Let me know if you are looking for something different.
-Blaise
Blaise - this is a great post. The SSWUG.org team decided to feature it on the site.
ReplyDeleteBlaise, This means that I will have to set my @XmlElement.name to be lower Cased Strings.
ReplyDeleteis there a way to prevent this restriction and still enjoy case insensitivity?
Hi eyal,
ReplyDeleteIf you are using EclipseLink JAXB (MOXy) then you can apply two sets of metadata to your object model.
1. The first set is with the proper case that will be used for marshalling.
2. The second is with all lower case that will be used for unmarshalling.
One set of metadata can be specified using annotations, and the second can be specified using MOXy's XML metadata format:
- Extending JAXB - Representing Metadata as XML
-Blaise
Thank You! The same what I was looking for!
ReplyDeleteIf I understand correctly, the limitation of this solution, is that your property names cannot be Camel Cased?
ReplyDeleteJust an aside, why doesn't JAXB have a case-insensitive option? Do we really think that when mapping Xml elements/attributes to class properties, that case matters? (i.e. would someone ever have 2 properties with the same name, and rely on upper/lower casing to differentiate them)?
Hi Aba,
ReplyDeleteIn this particular example the XML document would marshal with all node names as lower case. If you are using EclipseLink JAXB (MOXy) you could apply a second mapping using the external mapping document:
- Mapping Objects to Multiple XML Schemas - Weather Example
XML schemas are case sensitive so it makes sense for JAXB (at least by default) to be case sensitive. I have the following enhancement request open to add support for case insensitive unmarshalling. Please comment on or vote for this bug to help move it up the priority list:
- Bug 331241 - Enhancement Request: Case insensitive unmarshalling
-Blaise
I have spring MVC based REST webservices and I am using @RequestBody and @ResponseBody annotations. I have configured JAXB2Marshaller in Spring configuration so Spring will use this to unmarshal xml requests.
ReplyDeleteHow can I inject StreamReaderDelegate into spring configuration so that the JAXBMarshaller to achieve case insensitive unmarshalling?
Thanks
I'm not sure which mechanisms are available to you when creating RESTful Web Services in Spring. With Java EE and JAX-RS I would create a MessageBodyReader to hook in the necessary code. For an example MessageBodyReader see:
Delete- MOXy as Your JAX-RS JSON Provider - Server Side
-Blaise
Your JAXB blog posts are an endless supply of good information. This really helped me solve a problem I had. Works great, thanks!
ReplyDelete