Java XML and JSON: Document processing for Java SE, Part 1: SAXON and Jackson

Transforming and converting XML and JSON documents with SAXON and Jackson

1 2 Page 2
Page 2 of 2

Continuing, xsl:for-each select="current-group()" iterates over the author names in the current for-each-group iteration's group. The xsl:sort select="../@title" construct will sort the output book elements, specified via the subsequent <book title="{../@title}" /> construct, according to the book titles.

Transformation

Now let's attempt the transformation. Execute the following command:

java XSLTDemo books.xml books.xsl

Unfortunately, this transformation fails: you should observe output that identifies Apache Xalan as the transformer factory and an error message stating that xsl:for-each-group is not supported.

Let's try again. Assuming that saxon9he.jar and XSLTDemo.class are located in the current directory, execute the following command:

java -cp saxon9he.jar;. XSLTDemo books.xml books.xsl

This time, you should observe the following sorted and properly grouped output:

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   </head>
   <body>
      <author name="Don Crawford">
         <book title="Office 365: Migrating and Managing Your Business in the Cloud"></book>
      </author>
      <author name="Matthew Katzer">
         <book title="Office 365: Migrating and Managing Your Business in the Cloud"></book>
         <book title="Securing Office 365: Masterminding MDM and Compliance in the Cloud"></book>
      </author>
      <author name="Wallace Wang">
         <book title="Office 2019 For Dummies"></book>
      </author>
   </body>
</html>

Addendum to Chapter 11: Processing JSON with Jackson

Converting XML to JSON with Jackson

Java XML and JSON, Chapter 11, introduces Jackson, which provides APIs for parsing and creating JSON objects. It's also possible to use Jackson to convert XML documents to JSON documents.

In this section, I'll show you two ways to convert XML to JSON, first with data binding and then with tree traversal. I'll assume that you've read Chapter 11 and are familiar with Jackson. In order to follow these demos, you should have downloaded the following JAR files from the Maven repository:

  • jackson-annotations-2.9.7.jar
  • jackson-core-2.9.7.jar
  • jackson-databind-2.9.7.jar

You'll need a few additional JAR files, as well; most are common to both conversion techniques. I'll provide information on obtaining these JAR files shortly.

Convert XML to JSON with data binding

Data binding lets you map serialized data to a Java object. For example, suppose you have a small XML document that describes a single planet. Listing 4 presents this document.

Listing 4. planet.xml

<?xml version="1.0" encoding="UTF-8"?>
<planet>
    <name>Earth</name>
    <planet_from_sun>3</planet_from_sun>
    <moons>9</moons>
</planet>

Listing 5 presents an equivalent Java Planet class whose objects map to planet.xml's content.

Listing 5. Planet.java

public class Planet
{
   public String name;
   public Integer planet_from_sun;
   public Integer moons;
}

The conversion process requires that you first parse the XML into a Planet object. You can accomplish this task by working with the com.fasterxml.jackson.dataformat.xml.XmlMapper class, as follows:

XmlMapper xmlMapper = new XmlMapper();
XMLInputFactory xmlif = XMLInputFactory.newFactory();
FileReader fr = new FileReader("planet.xml");
XMLStreamReader xmlsr = xmlif.createXMLStreamReader(fr);
Planet planet = xmlMapper.readValue(xmlsr, Planet.class);

XmlMapper is a customized com.fasterxml.jackson.databind.ObjectMapper that reads and writes XML. It provides several readValue() methods for reading a single XML value from an XML-specific input source; for example:

<T> T readValue(XMLStreamReader r, Class<T> valueType)

Each readValue() method requires a javax.xml.stream.XMLStreamReader object as its first argument. This object is essentially a StAX-based stream-based parser for efficiently parsing text in a forward manner.

The second argument is a java.lang.Class object for the target type that is being instantiated, populated with XML data, and whose instance is subsequently returned from the method.

The bottom line of this code fragment is that Listing 4's content is read into a Planet object that readValue() returns to its caller.

Once the object has been created, it's easy to write it out as JSON by working with ObjectMapper and its String writeValueAsString(Object value) method:

ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(planet);

I excerpted these code fragments from an XML2JSON application whose complete source code appears in Listing 6.

Listing 6. XML2JSON.java (Version 1)

import java.io.FileReader;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import static java.lang.System.*;

public class XML2JSON
{
   public static void main(String[] args) throws Exception
   {
      XmlMapper xmlMapper = new XmlMapper();
      XMLInputFactory xmlif = XMLInputFactory.newFactory();
      FileReader fr = new FileReader("planet.xml");
      XMLStreamReader xmlsr = xmlif.createXMLStreamReader(fr);
      Planet planet = xmlMapper.readValue(xmlsr, Planet.class);
      ObjectMapper jsonMapper = new ObjectMapper();
      String json = jsonMapper.writeValueAsString(planet);
      out.println(json);
   }
}

Before you can compile Listings 5 and 6, you'll need to download Jackson Dataformat XML, which implements XMLMapper. I downloaded version 2.9.7, which matches the versions of the other three Jackson packages.

Assuming that you've successfully downloaded jackson-dataformat-xml-2.9.7.jar, execute the following command (spread over two lines for readability) to compile the source code:

javac -cp jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;jackson-dataformat-xml-2.9.7.jar;.
      XML2JSON.java

Before you can run the resulting application, you'll need to download Jackson Module: JAXB Annotations, and also download StAX 2 API. I downloaded JAXB Annotations version 2.9.7 and StAX 2 API version 3.1.3.

Assuming that you've successfully downloaded jackson-module-jaxb-annotations-2.9.7.jar and stax2-api-3.1.3.jar, execute the following command (spread across three lines for readability) to run the application:

java -cp jackson-annotations-2.9.7.jar;jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;
     jackson-dataformat-xml-2.9.7.jar;jackson-module-jaxb-annotations-2.9.7.jar; stax2-api-3.1.3.jar;.
     XML2JSON

If all goes well, you should observe the following output:

{"name":"Earth","planet_from_sun":3,"moons":9}

Convert XML to JSON with tree traversal

Another way to convert from XML to JSON is to first parse the XML into a tree of JSON nodes and then write this tree to a JSON document. You can accomplish the first task by calling one of XMLMapper's inherited readTree() methods:

XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(xml.getBytes());

ObjectMapper's JsonNode readTree(byte[] content) method deserializes JSON content into a tree of jackson.databind.JsonNode objects, and returns the root JsonNode object of this tree. In an XmlMapper context, this method deserializes XML content into the tree. In either case, the JSON or XML content is passed to this method as an array of bytes.

The second task -- converting the tree of objects to JSON -- is accomplished in a similar manner to what I previously showed. This time, it's the JsonNode root object that's passed to writeValueAsString():

ObjectMapper jsonMapper = new ObjectMapper();
String json = jsonMapper.writeValueAsString(node);

I excerpted these code fragments from an XML2JSON application whose complete source code appears in Listing 7.

Listing 7. XML2JSON.java (version 2)

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import static java.lang.System.*;

public class XML2JSON
{
   public static void main(String[] args) throws Exception
   {
      String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                   "<planet>\n" +
                   "    <name>Earth</name>\n" +
                   "    <planet_from_sun>3</planet_from_sun>\n" +
                   "    <moons>1</moons>\n" +
                   "</planet>\n";

      XmlMapper xmlMapper = new XmlMapper();
      JsonNode node = xmlMapper.readTree(xml.getBytes());
      ObjectMapper jsonMapper = new ObjectMapper();
      String json = jsonMapper.writeValueAsString(node);
      out.println(json);
   }
}

Execute the following command (spread over two lines for readability) to compile Listing 7:

javac -cp jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;jackson-dataformat-xml-2.9.7.jar
      XML2JSON.java

Before you can run the resulting application, you'll need to download Woodstox, which is a high-performance XML processor that implements StAX, SAX2, and StAX2. I downloaded Woodstox 5.2.0. Then execute the following command (spread across three lines for readability) to run the application:

java -cp jackson-annotations-2.9.7.jar;jackson-core-2.9.7.jar;jackson-databind-2.9.7.jar;
     jackson-dataformat-xml-2.9.7.jar;stax2-api-3.1.3.jar;woodstox-core-5.2.0.jar;.
     XML2JSON

If all goes well, you should observe the following output:

{"name":"Earth","planet_from_sun":"3","moons":"1"}

Notice that the numbers assigned to the planet_from_sun and moons XML elements are serialized to JSON strings instead of numbers. The readTree() method doesn't infer the data type in the absence of an explicit type definition.

Jackson's support for XML tree traversal has additional limitations:

  • Jackson is unable to differentiate between objects and arrays. Because XML provides no means to differentiate an object from a list (array) of objects, Jackson collates repeated elements into a single value.
  • Jackson doesn't support mixed content (textual content and elements as children of an element). Instead, it maps each XML element to a JsonNode object. Any text is lost.

Given these limitations, it's not surprising that the official Jackson documentation recommends against parsing XML into JsonNode-based trees. You're better off using the data binding conversion technique.

Conclusion

The material presented in this article should be considered as addendum to Chapters 6 and 11 in the second edition of Java XML and JSON. In contrast, my next article will be related to the book but entirely new material. Keep your eye out for my upcoming article about binding Java objects to JSON documents with JSON-B.

1 2 Page 2
Page 2 of 2