Using JSON with Liquid XML Objects
This tutorial explains how to use the JSON data format with Liquid XML Objects.
The article discusses round-tripping JSON and XML data, and demonstrates how to follow these steps:
Why Convert XML to JSON and JSON to XML?
Integrated heterogeneous computer systems often require data to be serialized in different formats in order to pass it between import and export functions of these systems. Two common data formats are JSON and XML and Liquid XML Objects provides the functionality to read and write data between these two formats. Using an XML Data Binding tool to perform this data conversion ensures conformance to the data model, speeding up development time and reducing errors.
Liquid XML Objects generates an XML Data Model from an XML Schema with primary functionality to read an XML document in to the XML Data Model, referred to as 'deserialize' the data, edit these objects via the API, and write the XML Data Model back to an XML document, referred to as 'serialize' the data.
Additionally, functionality is also included to allow a JSON document to be read in to the XML Data Model and written back to a JSON document, or to an XML document and vice versa.
The process of reading a data format, writing to a different data format and then having the ability to reproduce the original data format from the intermediate data is know as data round-tripping.
Round-tripping Data
Writing JSON data from the XML data model is fairly straight forward, however if you want to round-trip between JSON and XML it is not an exact science and certain combinations of constructs such as Sequence, Choice, Any or Mixed content in XML may not be feasible to round-trip as a JSON document.
As such, it is recommended that you use the default 'Simplified Model' when generating code that you want to round-trip.
JsonRoundTrip Enumeration
The LjSerializer class is used to read and write JSON data and serialize and deserialize methods take an LjSettings parameter. The settings class has a SupportRoundtrip property of type JsonRoundTrip Enumeration with the following possible values:
Member | Value | Description |
---|---|---|
Basic | 1 | Basic - JSON contains additional data for Attribute and Element Text support and is suitable for a simple data model. Attributes are prefixed with '@' and Element Text is named '#text'. |
Full | 2 | Full - JSON contains additional data for Attribute, Element Text, Namespace and Type support and is suitable for more complex data models. Attributes are prefixed with '@' and Element Text is named '#text'. Namespace prefix are written on each node. |
None | 0 | None - JSON contains no additional data. The JSON document is unlikely to be readable into the Liquid XML Objects model. |
The default value is JsonRoundTrip.Full which provides the best possible round-trip support.
Round-trip Example
XML Schema Set
This example uses an XML schema set that contains multiple namespaces in order to demonstrate the different output from using the JsonRoundTrip Enumeration.
Main.xsd
<?xml version="1.0" encoding="utf-16"?> <!-- Created with Liquid Studio (https://www.liquid-technologies.com) --> <xs:schema xmlns:ord="http://www.example.com/OrderTypes" xmlns:cmn="http://www.example.com/CommonTypes" xmlns:pur="http://www.example.com/Purchase" xmlns:cust="http://www.example.com/CustomerTypes" elementFormDefault="qualified" targetNamespace="http://www.example.com/Purchase" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import schemaLocation="CommonTypes.xsd" namespace="http://www.example.com/CommonTypes" /> <xs:import schemaLocation="CustomerTypes.xsd" namespace="http://www.example.com/CustomerTypes" /> <xs:import schemaLocation="OrderTypes.xsd" namespace="http://www.example.com/OrderTypes" /> <xs:element name="Purchase"> <xs:complexType> <xs:sequence> <xs:element name="OrderDetail" type="ord:OrderType" /> <xs:element name="PaymentMethod" type="cmn:PaymentMethodType" /> <xs:element ref="pur:CustomerDetails" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="CustomerDetails" type="cust:CustomerType" /> </xs:schema>
CommonTypes.xsd
<?xml version="1.0" encoding="utf-16"?> <!--Created with Liquid Studio (https://www.liquid-technologies.com)--> <xs:schema elementFormDefault="qualified" targetNamespace="http://www.example.com/CommonTypes" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="AddressType"> <xs:sequence> <xs:element name="Line1" type="xs:string" /> <xs:element name="Line2" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:simpleType name="PriceType"> <xs:restriction base="xs:decimal"> <xs:fractionDigits value="2" /> </xs:restriction> </xs:simpleType> <xs:simpleType name="PaymentMethodType"> <xs:restriction base="xs:string"> <xs:enumeration value="VISA" /> <xs:enumeration value="MasterCard" /> <xs:enumeration value="Cash" /> <xs:enumeration value="Amex" /> </xs:restriction> </xs:simpleType> </xs:schema>
CustomerTypes.xsd
<?xml version="1.0" encoding="utf-16"?> <!--Created with Liquid Studio (https://www.liquid-technologies.com)--> <xs:schema xmlns:cmn="http://www.example.com/CommonTypes" elementFormDefault="qualified" targetNamespace="http://www.example.com/CustomerTypes" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import schemaLocation="CommonTypes.xsd" namespace="http://www.example.com/CommonTypes" /> <xs:complexType name="CustomerType"> <xs:sequence> <xs:element name="Name" type="xs:string" /> <xs:element name="DeliveryAddress" type="cmn:AddressType" /> <xs:element name="BillingAddress" type="cmn:AddressType" /> </xs:sequence> </xs:complexType> </xs:schema>
OrderTypes.xsd
<?xml version="1.0" encoding="utf-16"?> <!--Created with Liquid Studio (https://www.liquid-technologies.com)--> <xs:schema xmlns:cmn="http://www.example.com/CommonTypes" elementFormDefault="qualified" targetNamespace="http://www.example.com/OrderTypes" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:import schemaLocation="CommonTypes.xsd" namespace="http://www.example.com/CommonTypes" /> <xs:complexType name="OrderType"> <xs:sequence> <xs:element name="Item" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="ProductName" type="xs:string" /> <xs:element name="Quantity"> <xs:complexType> <xs:simpleContent> <xs:extension base="xs:int"> <xs:attribute name="IsMultipack" type="xs:boolean" /> </xs:extension> </xs:simpleContent> </xs:complexType> </xs:element> <xs:element name="UnitPrice" type="cmn:PriceType" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute ref="cmn:OrderID" use="required" /> </xs:complexType> </xs:schema>
Generated Object Model
When we run our XML Schema through the Liquid XML Objects code generator using the default 'Simplified Model' option, it will generate a C# library with an object model as shown in Figure 2.
Sample XML Document
The following Sample XML document is valid against the above schema. We will use this XML document as our starting point to populate our XML Data Model, i.e. deserialize into the generated C# objects.
<?xml version="1.0" encoding="utf-8"?> <!-- Created with Liquid Studio (https://www.liquid-technologies.com) --> <pur:Purchase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cust="http://NamespaceTest.com/CustomerTypes" xmlns:ord="http://NamespaceTest.com/OrderTypes" xmlns:cmn="http://NamespaceTest.com/CommonTypes" xmlns:pur="http://NamespaceTest.com/Purchase"> <pur:OrderDetail> <ord:Item> <ord:ProductName>Item1</ord:ProductName> <ord:Quantity>10</ord:Quantity> <ord:UnitPrice>123.23</ord:UnitPrice> </ord:Item> <ord:Item> <ord:ProductName>Item2</ord:ProductName> <ord:Quantity IsMultipack="true">10</ord:Quantity> <ord:UnitPrice>99.77</ord:UnitPrice> </ord:Item> <ord:Item> <ord:ProductName>Item3</ord:ProductName> <ord:Quantity>56</ord:Quantity> <ord:UnitPrice>9.12</ord:UnitPrice> </ord:Item> </pur:OrderDetail> <pur:PaymentMethod>Cash</pur:PaymentMethod> <pur:CustomerDetails> <cust:Name>Mr S Smith</cust:Name> <cust:DeliveryAddress> <cmn:Line1>123 The Road</cmn:Line1> <cmn:Line2>My Town</cmn:Line2> </cust:DeliveryAddress> <cust:BillingAddress> <cmn:Line1>456 High Street</cmn:Line1> <cmn:Line2>Some Pace</cmn:Line2> </cust:BillingAddress> </pur:CustomerDetails> </pur:Purchase>
Serialize and Deserialize XML and JSON Data
We can now write the C# client executable source code to serialize and deserialze our XML and JSON data.
using LiquidTechnologies.XmlObjects; using LiquidXmlObjects.Main.Ord; using LiquidXmlObjects.Main.Pur; ... try { LxSerializer<PurchaseElm> xmlSerializer = new LxSerializer<PurchaseElm>(); LjSerializer<PurchaseElm> jsonSerializer = new LjSerializer<PurchaseElm>(); PurchaseElm purchaseElmFromXML; PurchaseElm purchaseElmFromJSON; // Step 1: read the XML document using (TextReader textReader = new StreamReader(@"C:\XSD Tutorial\SampleFile.xml")) { purchaseElmFromXML = xmlSerializer.Deserialize(textReader); } // Step 2:add a new item to the order purchaseElmFromXML.OrderDetail.Items.Add(new OrderTypeCt.ItemElm { ProductName = "Item4", Quantity = new OrderTypeCt.ItemElm.QuantityElm() { Value = 5 }, UnitPrice = 9.99 }); // Step 3: write a new XML document xmlSerializer.Serialize(@"C:\XSD Tutorial\SampleFile2.xml", purchaseElmFromXML); // Step 4: write a new JSON document jsonSerializer.Serialize(@"C:\XSD Tutorial\SampleFile.json", purchaseElmFromXML); // Step 5: read the JSON document using (TextReader textReader = new StreamReader(@"C:\XSD Tutorial\SampleFile.json")) { purchaseElmFromJSON = jsonSerializer.Deserialize(textReader); } // Step 6: write a new XML document xmlSerializer.Serialize(@"C:\XSD Tutorial\SampleFile3.xml", purchaseElmFromJSON); // Step 7: write a basic JSON document with JsonRoundTrip.Basic jsonSerializer.Serialize(@"C:\XSD Tutorial\SampleFileBasic.json", purchaseElmFromXML, new LjWriterSettings() { SupportRoundtrip = JsonRoundTrip.Basic }); // and write a vanilla JSON document with JsonRoundTrip.None jsonSerializer.Serialize(@"C:\XSD Tutorial\SampleFileNone.json", purchaseElmFromXML, new LjWriterSettings() { SupportRoundtrip = JsonRoundTrip.None }); } catch (LxException ex) { Debug.Fail(ex.Message); }
Step 1: read the XML document
Using a StreamReader, we can deserialize the SampleFile.xml into a PurchaseElm object.
Step 2: add a new item to the order
The purchaseElm is our entry point to the object model. We can use OrderDetail child object's Items collection to add a new Order.
purchaseElmFromXML.OrderDetail.Items.Add(new OrderTypeCt.ItemElm { ProductName = "Item4", Quantity = new OrderTypeCt.ItemElm.QuantityElm() { Value = 5 }, UnitPrice = 9.99 });
Step 3: write a new XML document
If we now serialize the object model to a new file SampleFile2.xml, we should see the additional Item 'Item4' has been added to the XML.
<pur:OrderDetail> ... <ord:Item> <ord:ProductName>Item4</ord:ProductName> <ord:Quantity>5</ord:Quantity> <ord:UnitPrice>9.99</ord:UnitPrice> </ord:Item> ... </pur:OrderDetail>
Step 4: write a new JSON document
We will now start to round-trip the data by serializing the data as JSON to SampleFile.json. Figure 5 shows the JSON data produced. Note the inclusion of namespace attributes, namespace prefixes on item names, '@' prefixes on attribute item names, and '#text' is used to name element text values.
This additional information will enable us to read the JSON data back into the XML Data Model.
{ "pur:Purchase": { "@xmlns:pur": "http://www.example.com/Purchase", "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", "@xmlns:cust": "http://www.example.com/CustomerTypes", "@xmlns:ord": "http://www.example.com/OrderTypes", "@xmlns:cmn": "http://www.example.com/CommonTypes", "pur:OrderDetail": { "@cmn:OrderID": "ABC123", "ord:Item": [ { "ord:ProductName": "Item1", "ord:Quantity": { "@IsMultipack": true, "#text": 10 }, "ord:UnitPrice": 123.23 }, { "ord:ProductName": "Item2", "ord:Quantity": 2, "ord:UnitPrice": 99.77 }, { "ord:ProductName": "Item3", "ord:Quantity": 56, "ord:UnitPrice": 9.12 }, { "ord:ProductName": "Item4", "ord:Quantity": 5, "ord:UnitPrice": 9.99 } ] }, "pur:PaymentMethod": "Cash", "pur:CustomerDetails": { "cust:Name": "Mr S Smith", "cust:DeliveryAddress": { "cmn:Line1": "123 The Road", "cmn:Line2": "My Town" }, "cust:BillingAddress": { "cmn:Line1": "456 High Street", "cmn:Line2": "Some Pace" } } } }
Step 5: read the JSON document
Using a StreamReader, we can deserialize the SampleFile.json into a new PurchaseElm object.
Step 6: write a new XML document
We can now serialize the object model created from the JSON data back to XML by writing the new file SampleFile3.xml.
Using the Liquid XML Diff tool, we can compare SampleFile2.xml and SampleFile3.xml to show that they are identical, and that the round-trip from XML to JSON and back to XML has been successful.
Step 7: write a basic JSON document with JsonRoundTrip.Basic and write a vanilla JSON document with JsonRoundTrip.None
Two other options exist when writing JSON. Figure 6 shows the output for JsonRoundTrip.Basic. As you can see, no namespace attributes are written out and no namespace prefixes are written on the item names. Attributes are still prefixed with '@' and '#text' is used as the item name for element text values. The JSON may still round-trip successfully for a simple schema that does not use namespaces.
{ "Purchase": { "OrderDetail": { "@OrderID": "ABC123", "Item": [ { "ProductName": "Item1", "Quantity": { "@IsMultipack": true, "#text": 10 }, "UnitPrice": 123.23 }, { "ProductName": "Item2", "Quantity": 2, "UnitPrice": 99.77 }, { "ProductName": "Item3", "Quantity": 56, "UnitPrice": 9.12 }, { "ProductName": "Item4", "Quantity": 5, "UnitPrice": 9.99 } ] }, "PaymentMethod": "Cash", "CustomerDetails": { "Name": "Mr S Smith", "DeliveryAddress": { "Line1": "123 The Road", "Line2": "My Town" }, "BillingAddress": { "Line1": "456 High Street", "Line2": "Some Pace" } } } }
The other option is to use and JsonRoundTrip.None. The only differences in the output for our schema between JsonRoundTrip.Basic and JsonRoundTrip.None would be the removal of the '@' prefix seen on the attributes OrderDetail and IsMultipack, and '#text' is no longer used to name element text values.
{ "Purchase": { "OrderDetail": { "OrderID": "ABC123", "Item": [ { "ProductName": "Item1", "Quantity": { "IsMultipack": true, "": 10 }, "UnitPrice": 123.23 }, ... }
JsonRoundTrip.None provides vanilla JSON which is unlikely to round-trip unless your schema does not use namespaces or attributes.