{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
]
}
,
{
"cell_type": "code",
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
}
},
"execution_count": null, "outputs": [],
"source": [
"#r \"nuget: FSharp.Data,6.6.0\"\n",
"\n",
"Formatter.SetPreferredMimeTypesFor(typeof\u003cobj\u003e, \"text/plain\")\n",
"Formatter.Register(fun (x: obj) (writer: TextWriter) -\u003e fprintfn writer \"%120A\" x)\n",
"#endif\n"
]
}
,
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Converting between JSON and XML\n",
"\n",
"[](https://mybinder.org/v2/gh/fsprojects/FSharp.Data/gh-pages?filepath=tutorials/JsonToXml.ipynb)\u0026emsp;\n",
"[](https://fsprojects.github.io/FSharp.Data//tutorials/JsonToXml.fsx)\u0026emsp;\n",
"[](https://fsprojects.github.io/FSharp.Data//tutorials/JsonToXml.ipynb)\n",
"\n",
"This tutorial shows how to implement convert JSON document (represented using\n",
"the [JsonValue](https://fsprojects.github.io/FSharp.Data/reference/fsharp-data-jsonvalue.html) type discussed in [JSON parser article](JsonValue.html)) to an\n",
"XML document (represented as `XElement`) and the other way round.\n",
"This functionality is not directly available in the FSharp.Data package, but it can\n",
"be very easily implemented by recursively walking over the JSON (or XML) document.\n",
"\n",
"If you want to use the JSON to/from XML conversion in your code, you can copy the\n",
"[source from GitHub](https://github.com/fsharp/FSharp.Data/blob/master/docs/content/tutorials/JsonToXml.fsx) and just include it in your project. If you use these\n",
"functions often and would like to see them in the FSharp.Data package, please submit\n",
"a [feature request](https://github.com/fsharp/FSharp.Data/issues).\n",
"\n",
"## Initialization\n",
"\n",
"We will be using the LINQ to XML API (available in `System.Xml.Linq.dll`) and the\n",
"[JsonValue](https://fsprojects.github.io/FSharp.Data/reference/fsharp-data-jsonvalue.html) which is available in the `FSharp.Data` namespace:\n",
"\n"
]
}
,
{
"cell_type": "code",
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
}
},
"execution_count": 2, "outputs": [],
"source": [
"#r \"System.Xml.Linq.dll\"\n",
"\n",
"open System.Xml.Linq\n",
"open FSharp.Data\n"
]
}
,
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this script, we create a conversion that returns an easy to process value, but the\n",
"conversion is not reversible (e.g. converting JSON to XML and then back to JSON will\n",
"produce a different value).\n",
"\n",
"## Converting XML to JSON\n",
"\n",
"Although XML and JSON are quite similar formats, there are a number of subtle differences.\n",
"In particular, XML distinguishes between **attributes** and **child elements**. Moreover,\n",
"all XML elements have a name, while JSON arrays or records are anonymous (but records\n",
"have named fields). Consider, for example, the following XML:\n",
"\n",
" [lang=xml]\n",
" \u003cchannel version=\"1.0\"\u003e\n",
" \u003ctitle text=\"Sample input\" /\u003e\n",
" \u003citem value=\"First\" /\u003e\n",
" \u003citem value=\"Second\" /\u003e\n",
" \u003c/channel\u003e\n",
"\n",
"The JSON that we produce will ignore the top-level element name (`channel`). It produces\n",
"a record that contains a unique field for every attribute and the name of a child element.\n",
"If an element appears multiple times, it is turned into an array:\n",
"\n",
" [lang=js]\n",
" { \"version\": \"1.0\",\n",
" \"title\": { \"text\": \"Sample input\" },\n",
" \"items\": [ { \"value\": \"First\" },\n",
" { \"value\": \"Second\" } ] }\n",
"\n",
"As you can see, the `item` element has been automatically pluralized to `items` and the\n",
"array contains two record values that consist of the `value` attribute.\n",
"\n",
"The conversion function is a recursive function that takes an `XElement` and produces\n",
"[JsonValue](https://fsprojects.github.io/FSharp.Data/reference/fsharp-data-jsonvalue.html). It builds JSON records (using `JsonValue.Record`) and arrays (using\n",
"`JsonValue.Array`). All attribute values are turned into `JsonValue.String` - the\n",
"sample does not implement a more sophisticated conversion that would turn numeric\n",
"attributes to a corresponding JSON type:\n",
"\n"
]
}
,
{
"cell_type": "code",
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
}
},
"execution_count": 3, "outputs": [],
"source": [
"/// Creates a JSON representation of a XML element\n",
"let rec fromXml (xml: XElement) =\n",
"\n",
" // Create a collection of key/value pairs for all attributes\n",
" let attrs =\n",
" [ for attr in xml.Attributes() -\u003e (attr.Name.LocalName, JsonValue.String attr.Value) ]\n",
"\n",
" // Function that turns a collection of XElement values\n",
" // into an array of JsonValue (using fromXml recursively)\n",
" let createArray xelems =\n",
" [| for xelem in xelems -\u003e fromXml xelem |] |\u003e JsonValue.Array\n",
"\n",
" // Group child elements by their name and then turn all single-\n",
" // element groups into a record (recursively) and all multi-\n",
" // element groups into a JSON array using createArray\n",
" let children =\n",
" xml.Elements()\n",
" |\u003e Seq.groupBy (fun x -\u003e x.Name.LocalName)\n",
" |\u003e Seq.map (fun (key, childs) -\u003e\n",
" match Seq.toList childs with\n",
" | [ child ] -\u003e key, fromXml child\n",
" | children -\u003e key + \"s\", createArray children)\n",
"\n",
" // Concatenate elements produced for child elements \u0026 attributes\n",
" Array.append (Array.ofList attrs) (Array.ofSeq children) |\u003e JsonValue.Record\n"
]
}
,
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Converting JSON to XML\n",
"\n",
"When converting JSON value to XML, we fact the same mismatch. Consider the following JSON value:\n",
"\n",
" [lang=js]\n",
" { \"title\" : \"Sample input\",\n",
" \"paging\" : { \"current\": 1 },\n",
" \"items\" : [ \"First\", \"Second\" ] }\n",
"\n",
"The top-level record does not have a name, so our conversion produces a list of `XObject`\n",
"values that can be wrapped into an `XElement` by the user (who has to specify the root\n",
"name). Record fields that are a primitive value are turned into attributes, while\n",
"complex values (array or record) become objects:\n",
"\n",
" [lang=xml]\n",
" \u003croot title=\"Sample input\"\u003e\n",
" \u003citems\u003e\n",
" \u003citem\u003eFirst\u003c/item\u003e\n",
" \u003citem\u003eSecond\u003c/item\u003e\n",
" \u003c/items\u003e\n",
" \u003cpaging current=\"1\" /\u003e\n",
" \u003c/root\u003e\n",
"\n",
"The conversion function is, again, implemented as a recursive function. This time, we use\n",
"pattern matching to distinguish between the different possible cases of [JsonValue](https://fsprojects.github.io/FSharp.Data/reference/fsharp-data-jsonvalue.html).\n",
"The cases representing a primitive value simply return the value as `obj`, while array\n",
"and record construct nested element(s) or attribute:\n",
"\n"
]
}
,
{
"cell_type": "code",
"metadata": {
"dotnet_interactive": {
"language": "fsharp"
},
"polyglot_notebook": {
"kernelName": "fsharp"
}
},
"execution_count": 4, "outputs": [],
"source": [
"/// Creates an XML representation of a JSON value (works\n",
"/// only when the top-level value is an object or an array)\n",
"let toXml (x: JsonValue) =\n",
" // Helper functions for constructing XML\n",
" // attributes and XML elements\n",
" let attr name value =\n",
" XAttribute(XName.Get name, value) :\u003e XObject\n",
"\n",
" let elem name (value: obj) =\n",
" XElement(XName.Get name, value) :\u003e XObject\n",
"\n",
" // Inner recursive function that implements the conversion\n",
" let rec toXml =\n",
" function\n",
" // Primitive values are returned as objects\n",
" | JsonValue.Null -\u003e null\n",
" | JsonValue.Boolean b -\u003e b :\u003e obj\n",
" | JsonValue.Number number -\u003e number :\u003e obj\n",
" | JsonValue.Float number -\u003e number :\u003e obj\n",
" | JsonValue.String s -\u003e s :\u003e obj\n",
"\n",
" // JSON object becomes a collection of XML\n",
" // attributes (for primitives) or child elements\n",
" | JsonValue.Record properties -\u003e\n",
" properties\n",
" |\u003e Array.map (fun (key, value) -\u003e\n",
" match value with\n",
" | JsonValue.String s -\u003e attr key s\n",
" | JsonValue.Boolean b -\u003e attr key b\n",
" | JsonValue.Number n -\u003e attr key n\n",
" | JsonValue.Float n -\u003e attr key n\n",
" | _ -\u003e elem key (toXml value))\n",
" :\u003e obj\n",
"\n",
" // JSON array is turned into a\n",
" // sequence of \u003citem\u003e elements\n",
" | JsonValue.Array elements -\u003e elements |\u003e Array.map (fun item -\u003e elem \"item\" (toXml item)) :\u003e obj\n",
"\n",
" // Perform the conversion and cast the result to sequence\n",
" // of objects (may fail for unexpected inputs!)\n",
" (toXml x) :?\u003e XObject seq\n"
]
}
,
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Related articles\n",
"\n",
"* API Reference: [JsonValue](https://fsprojects.github.io/FSharp.Data/reference/fsharp-data-jsonvalue.html)\n",
"\n",
"* [JSON Parser](../library/JsonValue.html) - a tutorial that introduces\n",
"`JsonValue` for working with JSON values dynamically.\n",
"\n",
"* [JSON Type Provider](../library/JsonProvider.html) - discusses F# type provider\n",
"that provides type-safe access to JSON data.\n",
"\n",
"* [XML Type Provider](../library/XmlProvider.html) - discusses the F# type provider\n",
"that provides type-safe access to XML data.\n",
"\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (F#)",
"language": "F#",
"name": ".net-fsharp"
},
"language_info": {
"file_extension": ".fs",
"mimetype": "text/x-fsharp",
"name": "polyglot-notebook",
"pygments_lexer": "fsharp"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "fsharp",
"items": [
{
"aliases": [],
"languageName": "fsharp",
"name": "fsharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}