Most Read This Week
From the Blogosphere
Solving the XML Problem with Jackson | @CloudExpo #API #Java #JSON #Cloud
Jackson is a popular library for handling JSON in Java applications, quickly becoming the de-facto standard in the ecosystem
By: Stackify Blog
Sep. 3, 2017 10:00 AM
Solving the XML Problem with Jackson
Jackson is a popular library for handling JSON in Java applications, quickly becoming the de-facto standard in the ecosystem. Starting with version 2, it has also introduced a mature XML implementation alongside its established JSON support.
Adding Jackson XML to the Project
And in Gradle:
This will automatically pull in all of the other Jackson dependencies that are needed:
Note that the Woodstox StAX library can be excluded and replaced with any other valid implementation of the StAX API.
Configuring the Jackson XML Module
The absolute simplest way of working with this is to just use the default configuration:
However, if we need additional configuration, we can instead construct the Jackson Module manually and set it up as necessary:
As of version 2.9.0, the only configuration options for the XML Module are:
Due to the fact that the XmlMapper extends the standard ObjectMapper, we can essentially use all of the standard Jackson ObjectMapper configuration settings and APISs.
For example, we can configure it to produce indented output as follows:
Note that some of these settings might not work with some XML implementations. For example, older versions of Jackson XML depended on Stax2Writer, which didn't support writing raw characters to the stream. This means that it doesn't support writing the raw newlines and indentations needed for the INDENT_OUTPUT feature to work.
As already mentioned, the XmlMapper object directly replaced ObjectMapper, only it works in terms of XML instead of JSON. This means that the API is exactly the same, and it can be used as a direct replacement.
The standard versions of this all exist and work as expected:
We can also read XML, using the various readValue APIs that are part of provided by the ObjectMapper.
For example, reading some XML from an InputStream into a Java Bean:
Again, the standard versions of this all exist, and work as expected:
Jackson Annotations for Serialization
This means that we can have one single set of beans, with one set of annotations and, depending on the ObjectMapper instance, we select whether we get XML or JSON. That's a huge benefit when it comes to structuring our code, as we no longer have to worry about translating between different beans that represent essentially the same data just to get different serialization formats.
For example, given the following bean:
will produce this JSON:
And this XML:
Additional Jackson Annotations for XML
@JacksonXmlProperty can be applied to any field in a bean to control the details of the element that is being rendered. This annotation allows us to determine the namespace, the local name, and whether the field is serialized as an Element or an Attribute. For example, the following bean:
For example, the following bean:
This generates the following XML output:
The @JacksonXmlRootElement has a similar role to the @JacksonXmlProperty but for the root element of the entire document. This can only adjust the Namespace and Local name - since the root element can never be serialized as an attribute.
For example, let's look at this Java POJO:
When serialized, this will result in the following XML:
Next, let's have a look at the @JacksonXmlText annotation.
Simply put, this indicates that an element should be rendered as plain text without another element containing it.
For example, the following POJO:
Will produce this simple XML output:
Naturally, you do have to be careful using this annotation and make sure you're still generating valid XML.
The @JacksonXmlCData annotation indicates that a CData wrapper should be placed around the content of the element. This can be used in conjunction with the @JacksonXmlText if desired to produce a CData wrapper without an element tag.
Let's have a look at a POJO using this annotation:
This will result in the following XML:
The @JacksonXmlElementWrapper annotation is used to override the default setting from setDefaultUseWrapper - as seen above. This can ensure that a collection either does or does not use a wrapper element, and can control what the wrapper element uses for namespace and local name.
When using wrapper elements, we get an additional element added which contains all of the elements from the collection, and when wrapper elements are not used then the individual collection elements are written directly inline:
This will produce the following XML:
Whereas, if the JacksonXmlElementWrapper is replaced with:
@JacksonXmlElementWrapper(useWrapping = false)
Then the XML produced won't contain the list element:
Supporting JAXB Annotations
The Jackson XML module also has the ability to support the standard JAXB annotations on our beans - instead of needing the Jackson specific ones. This can be useful if we want to use Jackson for the actual XML Serialization but don't want to depend on it at compile time.
This can also be used to allow JAXB to generate our bean definitions from an XML Schema and have Jackson process them.
This functionality is an additional module that needs to be added in for it to work. It doesn't work out of the box like the Jackson annotations do. In order to configure this - we need to add the JaxbAnnotationModule to our ObjectMapper as follows:
We can now write or generate a Java bean with JAXB annotations and simply process it with this XmlMapper.
For example, the following POJO:
Will produce the following XML when marshalled:
Partial Reading and Writing
The functionality makes good use of the standard XMLStreamWriter class, and naturally of XMLStreamReader as well. This cool functionality gives us a lot of flexibility to work with existing XML documents and integrate with these cleanly and easily.
In order to do this, the XmlMapper needs to be called to write values to the XMLStreamWriter object - the same as if we were writing to any other Writer:
This will produce the following XML:
Here, the XML Prolog, root element, and the comment - are not produced by Jackson XML, but everything inside the EmployeeBean element is.
This can be especially useful if we only care to have Java bean representations for data in the middle of a larger object - for example, if we are parsing an Atom wrapper around the data we are interested in.
In order to do this, the XmlMapper needs to be called to read values from the XMLStreamReader object - the same as if we were reading from any other Reader.
Let's have a look at a simple example. The following Java code will consume the XML generated above into an EmployeeBeen instance:
Limitations of the Jackson XML Module
Note that, unlike with JSON, the outermost object must be a bean type - it can not be a primitive or wrapper type, an enumeration, or a collection. This is a direct result of how XML works - there's no way in XML to represent such a top-level value.
By default, Jackson will always use a wrapper element for collections, which is also different to how JAXB works. This is the major way that the XML produced by Jackson is not compatible with the XML produced by JAXB. Of course, the behavior can be configured, using the JacksonXmlElementWrapper annotation for one field or the setDefaultUseWrapper configuration setting on the XmlMapper globally.
Jackson also has no support for working with specific XML Schemas. It's designed for writing Java Beans first, rather than generating the Java code from pre-existing schemas. Note that this can be solved to an extent by using the JAXB annotation support and generating the beans using the standard xjc tool.
Equally, it has no support for some of the more advanced XML tools - such as XPath or XSLT. If we need this level of support then we should instead use a more full-featured XML solution.
Usage on Android
If we're using the Woodstox XML library that Jackson XML depends on by default - there's nothing extra to be done. If, however, we're using an alternative library - then we might need to add that dependency manually:
And for Gradle:
Typically, this has to be handled using two different libraries with entirely separate configurations.
Finally, beyond flexibility and ease-of-use, the Jackson team has historically placed a strong emphasis on performance. And given that marshalling and unmarshalling of data is a large part of most web applications, choosing the right library to handle all of that work is critical. That, plus a performance monitoring tool such as Retrace will allow you to get the most out of your app.
Subscribe to the World's Most Powerful Newsletters