Transforming data: from JSON to XML and XML to JSON

Transforming data: from JSON to XML and XML to JSON

Transforming data from JSON to XML and XML to JSON with C#

Table of Contents

1 Objective
2 Data Transfer Object (DTO) and data samples
3 Root node / root property
3.1 Add root property to JSON
3.2 Remove root property from JSON
4 Transformation
4.1 Transform JSON into XML
4.2 Transform XML into JSON
5 Azure Function
6 Alternative - Newtonsoft
7 Complete solution

1 Objective

A JSON document has to be transformed into XML. If the XML is transformed back into JSON, the initial JSON has to equal the transformed back JSON. The solution will be provided as an Azure Function.

mermaid-diagram-DataTransformation.png

Additional requirements which have to be met:

  • JSON-Document has to contain a root property
  • Data types have to be specified (string, int, boolean,...)
  • XML Attributes has to be supported

2 Data Transfer Object (DTO) and data samples

To ensure that XML-attributes and data types can be used, a C# DTO is required. Like the required XML root node, the JSON document shall also have a root property.

Person class:

[XmlRoot("person")]
[JsonObject(Title = "Person")]
public class Person
{
    [XmlElement("firstname")]
    public string FirstName { get; set; }

    [XmlElement("lastname")]
    public string LastName { get; set; }

    [XmlAttribute("gender")]
    public string Gender { get; set; }

    [XmlElement("age")]
    public int Age { get; set; }
}

XML for Person class:

<?xml version="1.0" encoding="utf-8" ?>
<person gender="female">
  <firstname>Anna</firstname>
  <lastname>Smith</lastname>
  <age>42</age>
</person>

JSON for Person class:

{
  "Person": {
    "FirstName": "Anna",
    "LastName": "Smith",
    "Gender": "female",
    "Age": 42
  }
}

3 Root node / root property

Unfortunately, it's not straight forward to have a root property in JSON. Usually, I do not have to care about, because I'm only using JSON documents. Then I use a wrapper class:

public class PersonRoot
{
    public Person Person { get; set; }
}

public class Person
{
    public string FirstName { get; set; }

    // ...
}

In my current case, I do not like this way:

The class PersonRoot will only be used for the JSON data. For the XML data, it's useless.

A solution to solve this problem is using class attribute [JsonObject(Title = "Person")] and add some code.

3.1 Add root property to JSON

The code below creates a new JObject based on the JsonObject attribute and adds the initial JObject as a child by transforming XML into JSON:

Add root property:


[JsonObject(Title = "Person")]
public class Person
{
    // ... 
}



Person obj = new Person();
var jsonObjectAttribute = typeof(T).GetCustomAttribute(typeof(JsonObjectAttribute)) as JsonObjectAttribute;
var jsonValue = JValue.FromObject(obj);
var jsonObject =  new JObject(new JProperty(jsonObjectAttribute.Title, jsonValue));

3.2 Remove root property from JSON

The code below creates a new JObject based on the JSON document. For transforming JSON into XML, the root JSON property has to be removed. JsonObject attribute is used to get the JSON root property.

Remove root property:

[JsonObject(Title = "Person")]
public class Person
{
    // ... 
}

var jsonObjectAttribute = typeof(T).GetCustomAttribute(typeof(JsonObjectAttribute)) as JsonObjectAttribute;
JObject root = JObject.Parse(json);
var data = root[jsonObjectAttribute.Title];

4 Transformation

It's time to put all the things together.

4.1 Transform JSON into XML

The code removes the root property, deserializes the JSON document to the object and the XmlSerializer is used to create an XML string.

Transform JSON into XML:

var result = Transformer.TransformJsonToXml<Person>(json);
public static string TransformJsonToXml<T>(string json)
{
    // Remove root property
    var jsonObjectAttribute = typeof(T).GetCustomAttribute(typeof(JsonObjectAttribute)) as JsonObjectAttribute;
    JObject root = JObject.Parse(json);
    var data = root[jsonObjectAttribute.Title];

    var obj = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(data));

    var serializer = new XmlSerializer(typeof(T));
    var stringWriter = new StringWriter();
    using var xmlWriter = XmlWriter.Create(stringWriter);
    var xmlSerializerNamespaces = new XmlSerializerNamespaces();
    xmlSerializerNamespaces.Add("","");
    serializer.Serialize(xmlWriter, obj, xmlSerializerNamespaces);
    return stringWriter.ToString();
}

4.2 Transform XML into JSON

The code deserializes the XML document by using the XmlSerializer to the object, adds the root property and the serialized object will be returned.

Transform XML into JSON:

var result = Transformer.TransformXmlToJson<Person>(xml);
public static string TransformXmlToJson<T>(string xml)
{
    var serializer = new XmlSerializer(typeof(T));
    using var stringReader = new System.IO.StringReader(xml);
    using var xmlReader = XmlReader.Create(stringReader);
    var obj = (T)serializer.Deserialize(xmlReader);

    // Add root property
    var jsonObjectAttribute = typeof(T).GetCustomAttribute(typeof(JsonObjectAttribute)) as JsonObjectAttribute;
    var jsonValue = JValue.FromObject(obj);
    var jsonObject =  new JObject(new JProperty(jsonObjectAttribute.Title, jsonValue));

    var settings = new JsonSerializerSettings { Formatting = Newtonsoft.Json.Formatting.Indented };
    return JsonConvert.SerializeObject(jsonObject, settings);
}

5. Azure Function

This is straight forward for returning the JSON document:

 return new OkObjectResult(JObject.Parse(result));

image

For returning the XML document OkObjectResult cannot be used:

 return new ContentResult() { Content = result, ContentType = "application/xml", StatusCode = 200 };

image

6. Alternative - Newtonsoft

The Newtonsoft JSON library also supports transforming data into JSON or XML: Newtonsoft: Converting between JSON and XML

But there are a few things which do not match my requirements:

  • prefixed attributes
    • so I have to include additional code to get rid of this
  • no data types
    • all data types are strings

7. Complete solution

The complete solution can be found in my GitHub repository.