Skip to content
Dennis C. Mitchell edited this page Mar 25, 2019 · 1 revision

NetCoreTestingUtilities provides two classes for translating between JSON and a JXML. JXML (a made-up term) is an enhanced XML structure that contains processing instructions and namespace-specific elements and attributes sufficient to faithfully represent any JSON structure as XML and to fully (and efficiently) recover JSON from XML. For more information on JXML, see the "About JXML" section below.

Jxml Class

This class provides a method for translating JSON to JXML - a special form of XML that includes processing instructions and namespace-specific elements and attributes for fully recovering JSON from XML.

Setup

The package can be added to your .NET Core 2 project by adding the following element to the .csproj file of a unit test class:

  <ItemGroup>
    <PackageReference Include="EDennis.NetCoreTestingUtilities" Version="2.1.0.0" />
    ...
  </ItemGroup>

Afterward, the package can be imported into a test class as such:

using EDennis.NetCoreTestingUtilities.Json;

Jxml Methods

public static XmlDocument ToJxml(JToken jtoken)

This method produces a Jxml document (which uses System.Xml.XmlDocument) from a Json.NET JToken object.

public static JToken ToJson(XmlDocument doc)

This method produces a Json.NET JToken object from a Jxml document (which uses System.Xml.XmlDocument).

Here is an example unit test for the above methods (where each parameter is part of a file name):

[Theory]
[InlineData("persons")]
[InlineData("bookstore")]
[InlineData("colors")]
[InlineData("colors2")]
[InlineData("colors3")]
public void TestJxml(string file) {

    var json = File.ReadAllText($"{file}.json");
    var jtoken = JToken.Parse(json);
    json = jtoken.ToString();

    //convert to JXML
    XmlDocument doc = Jxml.ToJxml(jtoken);
    
    //convert back to JSON
    var jtoken2 = Jxml.ToJson(doc);
    var json2 = jtoken2.ToString();

    //compare formatted strings
    Assert.Equal(json, json2);
}

About JXML

JXML Processing Instructions

Embedded through the JXML document, special processing instructions include information such as where objects start and end and where particular arrays start and end (keeping proper track of nested arrays). Here is a list of JXML processing instructions:

  • <?object-start?> where a JSON object would begin (left-brace).
  • <?object-end?> where a JSON object would end (right-brace).
  • <?array-item #?> where a JSON array would begin (left bracket) or continue. # is a unique hashcode for the array.
  • <?array-end #?> where a JSON array would end (right bracket). # is a unique hashcode for the array.

JXML Elements

In a JXML document, the top levels are handled in a special way. The root element gets a generic JXML element. This ensures consistency and predictability in the structure. All items of top-level arrays and top-level nested arrays get a generic JXML element, also. These items would have no key in a JSON document; so, it is appropriate to represent them with special elements. Here is a list of JXML elements:

  • <jx-root></jx:root> the root element for the document.
  • <jx-object></jx:object> a stand-in element for objects without a key.
  • <jx-value></jx-value> a stand-in element for primitive values without a key.

JXML Type Attribute

Often XML does not include the data types of element text nodes. Without this data type information, one must resort to error-prone type inference methods. That said, it is usually acceptable to include data type information in an XML document. The perfect place to represent such metadata is in an attribute of an element. For all elements representing primitive types, JXML includes a jx-type attribute. Here is an example:

  • <PersonID jx-type='integer'>123</PersonID>

Here is the full list of type codes:

  • boolean
  • bytes
  • date
  • decimal
  • integer
  • string
  • null

JXML Sub-Sib Attribute

When representing array items in JXML, each item should have the same "key" -- that is, the same element name. For top-level arrays, the key is the generic jx-object element name or jx-value element name. For other arrays, it is a normal element name (e.g., PhoneNumber). When recovering JSON from JXML, you only want one represention of the key for the entire array. To aid in translating JXML to JSON, all array items except for the first item include a special jx-sub-sib (subsequent-sibling) attribute, whose value is set to true.

Example JXML Documents and Equivalent JSON Documents

Below are some examples of JXML documents and their equivalent representations in JSON. Please note that the placement of and processing instructions, which is not perfectly nested and partly overlapping with other tags, is done on purpose to preserve these instructions during possible XSLT operations, such as removing individual array items, and to facilitate re-constitution of JSON from the JXML.

<jx:root xmlns:jx="http://edennis.com/2013/jsonxml">
  <jx:object>
    <?array-item -1466776808?>
    <?object-start ?>
    <ID jx:type="integer">1</ID>
    <FirstName jx:type="string">Bob</FirstName>
    <LastName jx:type="string">Jones</LastName>
    <DateOfBirth jx:type="date">1980-01-23T00:00:00</DateOfBirth>
    <Skills>
      <?array-item 1780166160?>
      <?object-start ?>
      <Category jx:type="string">Application Development</Category>
      <Score jx:type="integer">3</Score>
      <?object-end ?>
    </Skills>
    <Skills jx:sub-sib="true">
      <?array-item 1780166160?>
      <?object-start ?>
      <Category jx:type="string">Project Management</Category>
      <Score jx:type="integer">3</Score>
      <?object-end ?>
    </Skills>
    <?array-end 1780166160?>
    <?object-end ?>
  </jx:object>
  <jx:object jx:sub-sib="true">
    <?array-item -1466776808?>
    <?object-start ?>
    <ID jx:type="integer">2</ID>
    <FirstName jx:type="string">Jill</FirstName>
    <LastName jx:type="string">Jones</LastName>
    <DateOfBirth jx:type="date">1981-01-24T00:00:00</DateOfBirth>
    <Skills>
      <?array-item -564334010?>
      <?object-start ?>
      <Category jx:type="string">Application Development</Category>
      <Score jx:type="integer">2</Score>
      <?object-end ?>
    </Skills>
    <Skills jx:sub-sib="true">
      <?array-item -564334010?>
      <?object-start ?>
      <Category jx:type="string">Project Management</Category>
      <Score jx:type="integer">1</Score>
      <?object-end ?>
    </Skills>
    <?array-end -564334010?>
    <?object-end ?>
  </jx:object>
  <?array-end -1466776808?>
</jx:root>

Equivalent JSON Document:

[
  {
    "ID": 1,
    "FirstName": "Bob",
    "LastName": "Jones",
    "DateOfBirth": "1980-01-23T00:00:00",
    "Skills": [
      {
        "Category": "Application Development",
        "Score": 3
      },
      {
        "Category": "Project Management",
        "Score": 3
      }
    ]
  },
  {
    "ID": 2,
    "FirstName": "Jill",
    "LastName": "Jones",
    "DateOfBirth": "1981-01-24T00:00:00",
    "Skills": [
      {
        "Category": "Application Development",
        "Score": 2
      },
      {
        "Category": "Project Management",
        "Score": 1
      }
    ]
  }
]

<jx:root xmlns:jx="http://edennis.com/2013/jsonxml">
  <?object-start ?>
  <store>
    <?object-start ?>
    <book>
      <?array-item 2042923742?>
      <?object-start ?>
      <category jx:type="string">reference</category>
      <author jx:type="string">Nigel Rees</author>
      <title jx:type="string">Sayings of the Century</title>
      <price jx:type="decimal">8.95</price>
      <?object-end ?>
    </book>
    <book jx:sub-sib="true">
      <?array-item 2042923742?>
      <?object-start ?>
      <category jx:type="string">fiction</category>
      <author jx:type="string">Evelyn Waugh</author>
      <title jx:type="string">Sword of Honour</title>
      <price jx:type="decimal">12.99</price>
      <?object-end ?>
    </book>
    <book jx:sub-sib="true">
      <?array-item 2042923742?>
      <?object-start ?>
      <category jx:type="string">fiction</category>
      <author jx:type="string">Herman Melville</author>
      <title jx:type="string">Moby Dick</title>
      <isbn jx:type="string">0-553-21311-3</isbn>
      <price jx:type="decimal">8.99</price>
      <?object-end ?>
    </book>
    <book jx:sub-sib="true">
      <?array-item 2042923742?>
      <?object-start ?>
      <category jx:type="string">fiction</category>
      <author jx:type="string">J. R. R. Tolkien</author>
      <title jx:type="string">The Lord of the Rings</title>
      <isbn jx:type="string">0-395-19395-8</isbn>
      <price jx:type="decimal">22.99</price>
      <?object-end ?>
    </book>
    <?array-end 2042923742?>
    <bicycle>
      <?object-start ?>
      <color jx:type="string">red</color>
      <price jx:type="decimal">19.95</price>
      <?object-end ?>
    </bicycle>
    <?object-end ?>
  </store>
  <?object-end ?>
</jx:root>

Equivalent JSON document:

{
  "store": {
    "book": [
      {
        "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      {
        "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      {
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      {
        "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}

<jx:root xmlns:jx="http://edennis.com/2013/jsonxml">
  <jx:value jx:type="string">
    <?array-item 917488290?>
    <?array-item 527658781?>
    <?array-item 1444778183?>black</jx:value>
  <jx:value jx:type="string" jx:sub-sib="true">
    <?array-item 1444778183?>white</jx:value>
  <?array-end 1444778183?>
  <?array-end 527658781?>
  <jx:value jx:type="string">
    <?array-item 917488290?>
    <?array-item 2109299352?>
    <?array-item 1478420174?>red</jx:value>
  <jx:value jx:type="string" jx:sub-sib="true">
    <?array-item 1478420174?>blue</jx:value>
  <jx:value jx:type="string" jx:sub-sib="true">
    <?array-item 1478420174?>green</jx:value>
  <?array-end 1478420174?>
  <jx:value jx:type="string">
    <?array-item 917488290?>
    <?array-item 2109299352?>
    <?array-item -1479112014?>cyan</jx:value>
  <jx:value jx:type="string" jx:sub-sib="true">
    <?array-item -1479112014?>magenta</jx:value>
  <jx:value jx:type="string" jx:sub-sib="true">
    <?array-item -1479112014?>yellow</jx:value>
  <jx:value jx:type="string" jx:sub-sib="true">
    <?array-item -1479112014?>black</jx:value>
  <?array-end -1479112014?>
  <?array-end 2109299352?>
  <?array-end 917488290?>
</jx:root>

Equivalent JSON document:

[
  [
    [
      "black",
      "white"
    ]
  ],
  [
    [
      "red",
      "blue",
      "green"
    ],
    [
      "cyan",
      "magenta",
      "yellow",
      "black"
    ]
  ]
]