This tutorial shows how to implement convert JSON document (represented using
the JsonValue type discussed in JSON parser article) to an
XML document (represented as XElement) and the other way round.
This functionality is not directly available in the FSharp.Data package, but it can
be very easily implemented by recursively walking over the JSON (or XML) document.
If you want to use the JSON to/from XML conversion in your code, you can copy the source from GitHub and just include it in your project. If you use these functions often and would like to see them in the FSharp.Data package, please submit a feature request.
Initialization
We will be using the LINQ to XML API (available in System.Xml.Linq.dll) and the
JsonValue which is available in the FSharp.Data namespace:
#r "System.Xml.Linq.dll"
open System.Xml.Linq
open FSharp.Data
In this script, we create a conversion that returns an easy to process value, but the conversion is not reversible (e.g. converting JSON to XML and then back to JSON will produce a different value).
Converting XML to JSON
Although XML and JSON are quite similar formats, there are a number of subtle differences. In particular, XML distinguishes between attributes and child elements. Moreover, all XML elements have a name, while JSON arrays or records are anonymous (but records have named fields). Consider, for example, the following XML:
<channel version="1.0">
<title text="Sample input" />
<item value="First" />
<item value="Second" />
</channel>
The JSON that we produce will ignore the top-level element name (channel). It produces
a record that contains a unique field for every attribute and the name of a child element.
If an element appears multiple times, it is turned into an array:
{ "version": "1.0",
"title": { "text": "Sample input" },
"items": [ { "value": "First" },
{ "value": "Second" } ] }
As you can see, the item element has been automatically pluralized to items and the
array contains two record values that consist of the value attribute.
The conversion function is a recursive function that takes an XElement and produces
JsonValue. It builds JSON records (using JsonValue.Record) and arrays (using
JsonValue.Array). All attribute values are turned into JsonValue.String - the
sample does not implement a more sophisticated conversion that would turn numeric
attributes to a corresponding JSON type:
/// Creates a JSON representation of a XML element
let rec fromXml (xml: XElement) =
// Create a collection of key/value pairs for all attributes
let attrs =
[ for attr in xml.Attributes() -> (attr.Name.LocalName, JsonValue.String attr.Value) ]
// Function that turns a collection of XElement values
// into an array of JsonValue (using fromXml recursively)
let createArray xelems =
[| for xelem in xelems -> fromXml xelem |] |> JsonValue.Array
// Group child elements by their name and then turn all single-
// element groups into a record (recursively) and all multi-
// element groups into a JSON array using createArray
let children =
xml.Elements()
|> Seq.groupBy (fun x -> x.Name.LocalName)
|> Seq.map (fun (key, childs) ->
match Seq.toList childs with
| [ child ] -> key, fromXml child
| children -> key + "s", createArray children)
// Concatenate elements produced for child elements & attributes
Array.append (Array.ofList attrs) (Array.ofSeq children) |> JsonValue.Record
Converting JSON to XML
When converting JSON value to XML, we fact the same mismatch. Consider the following JSON value:
{ "title" : "Sample input",
"paging" : { "current": 1 },
"items" : [ "First", "Second" ] }
The top-level record does not have a name, so our conversion produces a list of XObject
values that can be wrapped into an XElement by the user (who has to specify the root
name). Record fields that are a primitive value are turned into attributes, while
complex values (array or record) become objects:
<root title="Sample input">
<items>
<item>First</item>
<item>Second</item>
</items>
<paging current="1" />
</root>
The conversion function is, again, implemented as a recursive function. This time, we use
pattern matching to distinguish between the different possible cases of JsonValue.
The cases representing a primitive value simply return the value as obj, while array
and record construct nested element(s) or attribute:
/// Creates an XML representation of a JSON value (works
/// only when the top-level value is an object or an array)
let toXml (x: JsonValue) =
// Helper functions for constructing XML
// attributes and XML elements
let attr name value =
XAttribute(XName.Get name, value) :> XObject
let elem name (value: obj) =
XElement(XName.Get name, value) :> XObject
// Inner recursive function that implements the conversion
let rec toXml =
function
// Primitive values are returned as objects
| JsonValue.Null -> null
| JsonValue.Boolean b -> b :> obj
| JsonValue.Number number -> number :> obj
| JsonValue.Float number -> number :> obj
| JsonValue.String s -> s :> obj
// JSON object becomes a collection of XML
// attributes (for primitives) or child elements
| JsonValue.Record properties ->
properties
|> Array.map (fun (key, value) ->
match value with
| JsonValue.String s -> attr key s
| JsonValue.Boolean b -> attr key b
| JsonValue.Number n -> attr key n
| JsonValue.Float n -> attr key n
| _ -> elem key (toXml value))
:> obj
// JSON array is turned into a
// sequence of <item> elements
| JsonValue.Array elements -> elements |> Array.map (fun item -> elem "item" (toXml item)) :> obj
// Perform the conversion and cast the result to sequence
// of objects (may fail for unexpected inputs!)
(toXml x) :?> XObject seq
Related articles
- API Reference: JsonValue
-
JSON Parser - a tutorial that introduces
JsonValuefor working with JSON values dynamically. - JSON Type Provider - discusses F# type provider that provides type-safe access to JSON data.
- XML Type Provider - discusses the F# type provider that provides type-safe access to XML data.
namespace System
namespace System.Xml
namespace System.Xml.Linq
Multiple items
namespace FSharp
--------------------
namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data
--------------------
namespace Microsoft.FSharp.Data
val fromXml: xml: XElement -> JsonValue
Creates a JSON representation of a XML element
val xml: XElement
Multiple items
type XElement =
inherit XContainer
interface IXmlSerializable
new: other: XElement -> unit + 4 overloads
member AncestorsAndSelf: unit -> XElement seq + 1 overload
member Attribute: name: XName -> XAttribute
member Attributes: unit -> XAttribute seq + 1 overload
member DescendantNodesAndSelf: unit -> XNode seq
member DescendantsAndSelf: unit -> XElement seq + 1 overload
member GetDefaultNamespace: unit -> XNamespace
member GetNamespaceOfPrefix: prefix: string -> XNamespace
...
<summary>Represents an XML element. See XElement Class Overview and the Remarks section on this page for usage information and examples.</summary>
--------------------
XElement(other: XElement) : XElement
XElement(name: XName) : XElement
XElement(other: XStreamingElement) : XElement
XElement(name: XName, content: obj) : XElement
XElement(name: XName, content: obj array) : XElement
val attrs: (string * JsonValue) list
val attr: XAttribute
XElement.Attributes() : XAttribute seq
XElement.Attributes(name: XName) : XAttribute seq
property XAttribute.Name: XName with get
<summary>Gets the expanded name of this attribute.</summary>
<returns>An <see cref="T:System.Xml.Linq.XName" /> containing the name of this attribute.</returns>
property XName.LocalName: string with get
<summary>Gets the local (unqualified) part of the name.</summary>
<returns>A <see cref="T:System.String" /> that contains the local (unqualified) part of the name.</returns>
type JsonValue =
| String of string
| Number of decimal
| Float of float
| Record of properties: (string * JsonValue) array
| Array of elements: JsonValue array
| Boolean of bool
| Null
member Request: url: string * ?httpMethod: string * ?headers: (string * string) seq -> HttpResponse
member RequestAsync: url: string * ?httpMethod: string * ?headers: (string * string) seq -> Async<HttpResponse>
member ToString: saveOptions: JsonSaveOptions * ?indentationSpaces: int -> string + 2 overloads
member WriteTo: w: TextWriter * saveOptions: JsonSaveOptions * ?indentationSpaces: int -> unit
static member AsyncLoad: uri: string * ?encoding: Encoding -> Async<JsonValue>
static member Load: stream: Stream -> JsonValue + 2 overloads
static member Parse: text: string -> JsonValue
static member ParseMultiple: text: string -> JsonValue seq
static member TryParse: text: string -> JsonValue option
<summary>
Represents a JSON value. Large numbers that do not fit in the
Decimal type are represented using the Float case, while
smaller numbers are represented as decimals to avoid precision loss.
</summary>
union case JsonValue.String: string -> JsonValue
<summary>
A JSON string value
</summary>
property XAttribute.Value: string with get, set
<summary>Gets or sets the value of this attribute.</summary>
<exception cref="T:System.ArgumentNullException">When setting, the <paramref name="value" /> is <see langword="null" />.</exception>
<returns>A <see cref="T:System.String" /> containing the value of this attribute.</returns>
val createArray: xelems: #XElement seq -> JsonValue
val xelems: #XElement seq
val xelem: #XElement
union case JsonValue.Array: elements: JsonValue array -> JsonValue
<summary>
A JSON array of values
</summary>
val children: (string * JsonValue) seq
XContainer.Elements() : XElement seq
XContainer.Elements(name: XName) : XElement seq
module Seq from Microsoft.FSharp.Collections
val groupBy: projection: ('T -> 'Key) -> source: 'T seq -> ('Key * 'T seq) seq (requires equality)
val x: XElement
property XElement.Name: XName with get, set
<summary>Gets or sets the name of this element.</summary>
<returns>An <see cref="T:System.Xml.Linq.XName" /> that contains the name of this element.</returns>
val map: mapping: ('T -> 'U) -> source: 'T seq -> 'U seq
val key: string
val childs: XElement seq
val toList: source: 'T seq -> 'T list
val child: XElement
val children: XElement list
module Array from Microsoft.FSharp.Collections
val append: array1: 'T array -> array2: 'T array -> 'T array
val ofList: list: 'T list -> 'T array
val ofSeq: source: 'T seq -> 'T array
union case JsonValue.Record: properties: (string * JsonValue) array -> JsonValue
<summary>
A JSON object, represented as an array of name-value pairs
</summary>
val toXml: x: JsonValue -> XObject seq
Creates an XML representation of a JSON value (works
only when the top-level value is an object or an array)
val x: JsonValue
val attr: name: string -> value: 'a -> XObject
val name: string
val value: 'a
Multiple items
type XAttribute =
inherit XObject
new: other: XAttribute -> unit + 1 overload
member Remove: unit -> unit
member SetValue: value: obj -> unit
member ToString: unit -> string
static member op_Explicit: attribute: XAttribute -> bool + 24 overloads
member IsNamespaceDeclaration: bool
member Name: XName
member NextAttribute: XAttribute
member NodeType: XmlNodeType
...
<summary>Represents an XML attribute.</summary>
--------------------
XAttribute(other: XAttribute) : XAttribute
XAttribute(name: XName, value: obj) : XAttribute
type XName =
interface IEquatable<XName>
interface ISerializable
member Equals: obj: obj -> bool
member GetHashCode: unit -> int
member ToString: unit -> string
static member (<>) : left: XName * right: XName -> bool
static member (=) : left: XName * right: XName -> bool
static member Get: expandedName: string -> XName + 1 overload
static member op_Implicit: expandedName: string -> XName
member LocalName: string
...
<summary>Represents a name of an XML element or attribute.</summary>
XName.Get(expandedName: string) : XName
XName.Get(localName: string, namespaceName: string) : XName
type XObject =
interface IXmlLineInfo
member AddAnnotation: annotation: obj -> unit
member Annotation: ``type`` : Type -> obj + 1 overload
member Annotations: ``type`` : Type -> obj seq + 1 overload
member RemoveAnnotations: ``type`` : Type -> unit + 1 overload
member BaseUri: string
member Document: XDocument
member NodeType: XmlNodeType
member Parent: XElement
member Changed: EventHandler<XObjectChangeEventArgs>
...
<summary>Represents a node or an attribute in an XML tree.</summary>
val elem: name: string -> value: obj -> XObject
val value: obj
type obj = System.Object
val toXml: (JsonValue -> obj)
union case JsonValue.Null: JsonValue
<summary>
A JSON null value
</summary>
union case JsonValue.Boolean: bool -> JsonValue
<summary>
A JSON boolean value
</summary>
val b: bool
union case JsonValue.Number: decimal -> JsonValue
<summary>
A JSON number stored as a decimal (used for numbers that fit in the decimal range)
</summary>
val number: decimal
union case JsonValue.Float: float -> JsonValue
<summary>
A JSON number stored as a float (used for large numbers that do not fit in decimal)
</summary>
val number: float
val s: string
val properties: (string * JsonValue) array
val map: mapping: ('T -> 'U) -> array: 'T array -> 'U array
val value: JsonValue
val n: decimal
val n: float
val elements: JsonValue array
val item: JsonValue
Multiple items
val seq: sequence: 'T seq -> 'T seq
--------------------
type 'T seq = System.Collections.Generic.IEnumerable<'T>