Converting between XML and JSON

Since JSON formatted data is likely to be needed only when communicating with a partner system, we'll assume that it's most common to convert between XML and JSON in Oracle Service Bus. For working with XML, we'll use XMLBeans, as that's what OSB uses. For parsing and generating JSON, we'll use Jackson (Version 1.9.x).

Getting ready

This recipe assumes the use of an existing OSB configuration project within the OSB workshop for development. So, ensure that you have installed and familiarized yourself with it prior to beginning.

Schema

If you wish to follow along exactly with these instructions you will require a copy of the schema and WSDL files used in this recipe. Copies of these are included with the code samples for the book.

Note

For the purpose of this example, the JSON format will map very closely to the XML schema, but this need not necessarily be the case, and the approach demonstrated next is flexible enough to allow for arbitrarily complex mapping.

Java libraries

Oracle Service Bus uses XMLBeans to provide its XML/object mapping. We'll use the same library to make our task simpler.

In order to map between JSON and objects, we'll use the Jackson library. Download Version 1.9.x of the Jackson Core ASL and Mapper ASL libraries from http://wiki.fasterxml.com/JacksonDownload.

How to do it...

  1. From the context menu in the Package Explorer view, select New | Java Project. For this example, the project is named CreditCardServiceMessages. Keep the remaining defaults and click on Next.
  2. For the Java Settings, set the Default output folder to CreditCardServiceMessages/build/classes. Click on Finish.
  3. For setting up the project structure, select CreditCardServiceMessages within the Package Explorer view, right-click and select New | Folder. Name the folder dist and click on OK.
  4. Repeat this process to create the folders genbuild, gensrc, lib, resources, and test in the Java project.

    Next, we need to import the schema and WSDL files we are using for this example.

  5. Within the Package Explorer view, select resources, right-click and select Import. This will open the Import Wizard. Select General | File System as the source and click on Next.
  6. Browse to the file directory containing the code samples for this chapter, and within the directory getting ready, select the folder resources.
  7. Within the Import window, ensure resources is checked and the option Create selected folder structure is selected (as circled in the following screenshot) and click on Finish.
    How to do it...
  8. To import the JAR files, select lib within the Package Explorer view, right-click and select Import. In the Import wizard, select General | File system, and click on Next.
  9. Browse to the directory <MIDDLEWARE_HOME>/osb/modules
  10. Click on OK and select the file com.bea.core.xml.xmlbeans_2.1.0.0_2-5-1.jar and click on Finish. This will create a copy of the JAR file in the lib folder within the Java project.
  11. Repeat this step to import the jackson-core-asl-1.9.x.jar and jackson-mapper-asl-1.9.x.jar libraries. Add the libraries to the projects build path by selecting the three files, right-clicking, and selecting Build Path | Add to Build Path.

    The structure of our Java Project should resemble the following screenshot:

    How to do it...
  12. We'll use Ant to build our project. Within the Package Explorer view, select CreditCardServiceMessages, right-click and select Import. This will open the Import Wizard. Select General | File System as the source and click on Next.
  13. Browse to the file directory containing the code samples for this chapter and select the folder getting ready. Within the Import window, ensure build.xml is checked and click on Finish.

    The most important target to note at this point within the build.xml file is scomp.

    <taskdef name="xmlbean"
      classname="org.apache.xmlbeans.impl.tool.XMLBean"
      classpath="${lib}/com.bea.core.xml.xmlbeans_2.1.0.0_2-5-1.jar" />
    
    <!-- Compile the config schema definition with XmlBeans -->
    <target name="scomp" depends="init" description="compile xsd">
        <xmlbean srcgendir="${gensrc}" classgendir="${genbuild}"
          destfile="${dist}/${ant.project.name}XmlBeans_1.0.jar"
          failonerror="true" classpathref="project.class.path">
    
          <fileset dir="${schemadir}"
          includes="wsdl/CreditCardService.xsd" />
       </xmlbean>
    </target>

    This compiles the schemas we have imported using XmlBeans, and will be required for subsequent steps.

  14. To compile the schemas, select Window | Show View | Ant and open the Ant View in our Eclipse perspective; next drag the build.xml file into the Ant View, and double-click on the scomp target.

    This will run the scomp target, which will use xmlbeans to generate Java classes which represent the schema types in our imported schemas and package them into the JAR file, named CreditCardServiceMessagesXmlBeans_1.0.jar.

    You should see output similar to the following, in the Console view:

    How to do it...

    The generated JAR file will be placed in the dist directory (refresh the project structure view if you can't see it).

  15. Right-click on this file, and select Build Path | Add to Build Path from the context menu, as we'll be writing classes that depend on this library later.
  16. Next, we need to create Plain Old Java Objects (POJOs) that represent the JSON objects that we will later be exchanging. These classes have no knowledge of the Jackson libraries that we will be using to convert between the object and JSON representations.

    For our example we will create the following classes:

    • CreditCard
    • DebitCreditCard
    • DebitCreditCardResponse

    An excerpt from the CreditCard class is shown as follows:

    package com.rubiconred.ckbk.creditcardsvc.pojo;
    
    public class CreditCard {
    
      private String cardType;
      private String cardHolderName;
      private String cardNumber;
      private Integer expiryMonth;
      private Integer expiryYear;
      private String securityNo;
    
      public String getCardType() {
        return cardType;
      }
    
      public void setCardType(String cardType) {
        this.cardType = cardType;
      }
      public String getCardHolderName() {
        return cardHolderName;
      }
    
       // …
    }

    Note

    Primitive types are not used for the previous numeric values. It is common for values in JSON objects to be optional, and either be omitted entirely from the serialised representation, or be serialised with a value of null. Java's primitive types cannot represent the absence of a value, so the object types should in general be used for numeric.

  17. Next we need to create the Java class CreditCardServiceMapperFactory that will be used to convert between the previous POJOs and their JSON representations.

    A snapshot of the code to do this is shown as follows. The full source code is provided in the sample for the chapter.

    package com.rubiconred.ckbk.creditcardsvc.json;
    
    import …
    
    public class CreditCardServiceMapperFactory {
    
      private static ObjectMapper mapper;
      private static ObjectReader debitCreditCardReader;
      private static ObjectWriter debitCreditCardWriter;
    
      static {
        mapper = new ObjectMapper();
    
        // Include null values in generated JSON
        mapper.setSerializationConfig(mapper.getSerializationConfig()
          .withSerializationInclusion(Inclusion.ALWAYS));
    
        debitCreditCardReader = mapper.reader(DebitCreditCard.class);
        debitCreditCardWriter = mapper.writerWithType(DebitCreditCard.class);
    
        // …
      }
    
      public static ObjectReader getDebitCreditCardReader() {
        return debitCreditCardReader;
      }
    
      public static ObjectWriter getDebitCreditCardWriter() {
        return debitCreditCardWriter;
      }
    
      // …
    }

    Jackson makes this very straightforward. The ObjectReader and ObjectWriter instances that we create are immutable, so they're thread-safe and can be shared as required.

    The ObjectMapper is the object on which the details of the JSON (de-) serialization are configured. For this example, we'll configure the mapper to include null values, rather than omitting them.

    Now that we have the necessary scaffolding in place, we can write the code that will convert between the XML format (exposed as an XMLBeans XmlObject instance) and the JSON format (represented by the POJOs we created earlier). This is the code that will later be invoked using Java Callout actions in OSB proxy services.

  18. In order to convert from JSON to XML, we create a method that accepts the JSON in a String, and uses the appropriate ObjectReader to parse it into the POJO we created earlier.

    The fields of the POJO are used to populate a new instance of the appropriate XmlObject. This is illustrated in the following method:

    public static XmlObject debitCreditCardJsonToXml(String json) {
      ObjectReader reader = CreditCardServiceMapperFactory
        .getDebitCreditCardReader();
    
      DebitCreditCardDocument debitDoc = DebitCreditCardDocument 
        .Factory.newInstance();
      DebitCreditCard jsonDebitCreditCard;
      TDebitCreditCard xmlDebitCreditCard;
      TCreditCard xmlCreditCard;
    
      try {
        jsonDebitCreditCard = reader.readValue(json);
        CreditCard jsonCreditCard = jsonDebitCreditCard 
          .getCreditCard();
        xmlDebitCreditCard = TDebitCreditCard.Factory
          .newInstance();
        xmlCreditCard = TCreditCard.Factory.newInstance();
        xmlCreditCard.setCardHolderName(jsonCreditCard 
          .getCardHolderName());
        xmlCreditCard.setCardNumber(jsonCreditCard 
          .getCardNumber());
        xmlCreditCard.setCardType(jsonCreditCard 
          .getCardType());
    
        // Set Remainder of Credit Card Details…
    
        xmlDebitCreditCard.setCreditCard(xmlCreditCard);
    
        Double trnAmount =jsonDebitCreditCard.getTrnAmount();
        if (trnAmount != null) {
          xmlDebitCreditCard.setTrnAmount( 
            BigDecimal.valueOf(trnAmount));
        }
        xmlDebitCreditCard.setTrnDesc(jsonDebitCreditCard 
         .getTrnDesc());
    
        debitDoc.setDebitCreditCard(xmlDebitCreditCard);
    
      } catch (JsonProcessingException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
      return debitDoc;
    }
  19. To convert from JSON to XML we simply use the following code fragment:
    DebitCreditCardDocument doc;
    doc = (DebitCreditCardDocument) DebitCreditCardConverter
      .debitCreditCardJsonToXml(DEBIT_CREDIT_CARD_JSON_STRING);
    
    TDebitCreditCard debitCreditCard = doc.getDebitCreditCard();

    Where DEBIT_CREDIT_CARD_JSON_STRING contains the JSON to convert. The result of this debitCreditCard is an XMLBeans generated class that gives us a Java wrapper around the ML conversion of debitCreditCard with JavaBeans-style accessors.

  20. Converting from XML to JSON uses the same approach, starting from an XmlObject and producing a String containing JSON, as illustrated in the following method:
    public static String debitCreditCardXmlToJson(XmlObject xml) 
    {
      ObjectWriter writer = CreditCardServiceMapperFactory
        .getDebitCreditCardWriter();
    
      DebitCreditCard debitCreditCard = new DebitCreditCard();
      String json = null;
      DebitCreditCardDocument debitCreditCardDoc;
      TDebitCreditCard source = null;
      XmlObject doc = null;
    
      try {
        doc = XmlObject.Factory.parse(xml.newXMLStreamReader());
    
        if (doc instanceof DebitCreditCardDocument) {
          debitCreditCardDoc = (DebitCreditCardDocument) doc;
          source = debitCreditCardDoc.getDebitCreditCard();
          TCreditCard sourceCC = source.getCreditCard();
          BigDecimal trnAmount = source.getTrnAmount();
    
          if (trnAmount != null) {
            debitCreditCard.setTrnAmount(trnAmount 
            .doubleValue());
          }
          debitCreditCard.setTrnDesc(source.getTrnDesc());
          CreditCard creditCard = new CreditCard();
    
          creditCard.setCardHolderName(sourceCC  
            .getCardHolderName());
          creditCard.setCardNumber(sourceCC.getCardNumber());
          creditCard.setCardType(sourceCC.getCardType());
          creditCard.setExpiryMonth(sourceCC.getExpiryMonth());
          creditCard.setExpiryYear(sourceCC.getExpiryYear());
          creditCard.setSecurityNo(sourceCC.getSecurityNo());
    
          debitCreditCard.setCreditCard(creditCard);
          json = writer.writeValueAsString(debitCreditCard);
        } else {
          System.out.println("debitCreditCardXmlToJson(): PARSE FAILED!!!");
        }
      } catch (XmlException e) {
        e.printStackTrace();
      } catch (JsonGenerationException e) {
        e.printStackTrace();
      } catch (JsonMappingException e) {
        e.printStackTrace();
      } catch (IOException e) {
        e.printStackTrace();
      }
    
      return json;
  21. To convert from XML to JSON we simply use the following code fragment:
    XmlObject debitCreditCardXmlObject; 
    debitCreditCardXmlObject = XmlObject.Factory
      .parse(DEBIT_CREDIT_CARD_XML);
    String json = DebitCreditCardConverter
      .debitCreditCardXmlToJson(debitCreditCardXmlObject);

    Where DEBIT_CREDIT_CARD_XML contains the XML to convert, the result of this JSON is a string containing the JSON representation of our XML object.

    Tip

    It is important to keep in mind, when implementing these conversions, that some incoming values may be null. One example of such an issue is when working with numeric values parsed from XML by XMLBeans; they will typically be instances of BigDecimal or BigInteger. Should you want to assign these values to a Double or Integer in your own objects, you must ensure that the returned value is not null before invoking the doubleValue() or intValue() methods.

Running the dist Ant task will produce a JAR file CreditCardServiceMessages_1.0.jar in the dist directory. This will be used later, along with the previously generated CreditCardServiceMessagesXmlBeans_1.0.jar, to perform the conversions between XML and JSON inside the OSB proxy services.

How it works...

We use XMLBeans to parse and generate XML, and we use Jackson to parse and generate JSON. We then implement an adapter that takes care of the translation between the two representations.

There's more...

It should be noted that there are other simpler frameworks which provide a mechanism for converting between XML and JSON, for example:

Given the simplicity of the previous example, any of these approaches would be fine. However, for more complex scenarios we find that Jackson provides the most control (it's also extremely fast – but that tends to be less relevant).

For example, Jackson provides many features for configuring the serialization and deserialization of your objects. In the event that the default behavior isn't appropriate for your use case, there are many options built-in, and custom (de-) serializers can be built very easily.

The final reason for leaning towards Jackson, is that it's leveraged by Coherence for its REST interface, so is a tried and tested component within the context of the Oracle stack.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.147.193