F# Data: JSON と XML の相互変換
このチュートリアルではJSONドキュメント( JSON パーサーの記事
で説明している JsonValue
型で表されるドキュメント)と
XMLドキュメント( XElement
で表されるドキュメント)を
相互に変換する機能を実装する方法を紹介します。
この機能はF# Dataライブラリから直接使用できるものではありませんが、
JSON(あるいはXML)ドキュメントを再帰的に処理するだけで
非常に簡単に実装できます。
JSONとXML間の変換を自身のコードで使いたい場合には GitHubにあるソース をコピーしてプロジェクトに追加するだけです。 この機能を頻繁に利用することがあり、F# Dataライブラリの機能としてほしい場合には 是非 機能リクエスト を投稿してください。
初期化
ここでは( System.Xml.Linq.dll
内にある)LINQ to XMLのAPIと、
FSharp.Data
名前空間にある JsonValue
を使います:
1: 2: 3: 4: |
#r "System.Xml.Linq.dll" #r "../../../../bin/FSharp.Data.dll" open System.Xml.Linq open FSharp.Data |
このスクリプト内では簡単に処理できる値を返すような変換機能を実装しますが、 リバーシブルな変換ではないことに注意してください (つまりJSONからXMLに変換した後、JSONに戻したとしても 元と同じ値になるとは限らないということです)。
XMLからJSONへの変換
XMLとJSONはかなり似た形式ではありますが、 細かい部分ではそれなりに違いがあります。 たとえばXMLでは 属性 と 子要素 が区別されます。 さらにすべてのXML要素には名前がありますが、 JSONの配列やレコードは無名です(ただしレコードには 名前のつけられたフィールドがあります)。 たとえば以下のようなXMLがあるとします:
<channel version="1.0">
<title text="Sample input" />
<item value="First" />
<item value="Second" />
</channel>
これに対して生成するJSONではトップレベルの要素名( channel
)が無視されます。
生成されるJSONデータはレコード型で、
各属性および子要素に対してユニークなフィールドが含まれます。
ある要素が繰り返し現れる場合には配列へと変換されます:
{ "version": "1.0",
"title": { "text": "Sample input" },
"items": [ { "value": "First" },
{ "value": "Second" } ] }
このように、 item
要素は自動的に items
という複数形に変換されて、
value
属性の値に関連づけられた2つのレコード値が
配列の要素として含まれるようになります。
変換関数は XElement
を引数にとり、 JsonValue
を返すような再帰関数です。
この関数は( JsonValue.Record
と JsonValue.Array
を使って)JSONのレコードと
配列を組み立てます。
すべての属性は JsonValue.String
に変換されます。
ただし今回の例では数値型を適切なJSON型に変換するような機能は実装しません:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: |
/// XML要素に対応するJSON表現を作成する let rec fromXml (xml:XElement) = // すべての属性に対してキー値ペアのコレクションを作成する let attrs = [ for attr in xml.Attributes() -> (attr.Name.LocalName, JsonValue.String attr.Value) ] // XElementのコレクションを(fromXmlを再帰的に使って) // JsonValueの配列に変換する関数 let createArray xelems = [| for xelem in xelems -> fromXml xelem |] |> JsonValue.Array // 子要素を名前でグループ化した後、 // 単一要素のグループを(再帰的に)レコードへと変換し、 // 複数要素のグループをcreateArrayでJSON配列に変換する 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 ) // 子要素および属性用に生成された要素を連結する Array.append (Array.ofList attrs) (Array.ofSeq children) |> JsonValue.Record |
JSONからXMLへの変換
JSONからXMLへ変換する場合、同じようなミスマッチが起こります。 たとえば以下のようなJSONデータがあるとします:
{ "title" : "Sample input",
"paging" : { "current": 1 },
"items" : [ "First", "Second" ] }
トップレベルのレコードには名前がないため、
今回の変換機能では(ルート名を指定することになる)ユーザー側で
XElement
としてラップできるような
XObject
のリストを生成することにします。
レコード内にあるプリミティブな値のフィールドは属性になり、
(配列やレコードのような)複雑な値はオブジェクトになります:
<root title="Sample input">
<items>
<item>First</item>
<item>Second</item>
</items>
<paging current="1" />
</root>
変換関数はやはり再帰関数になります。
今回はパターンマッチを使って JsonValue
のそれぞれ取り得るケースを区別します。
プリミティブ値を表すケースでは単に値を obj
として返し、
配列やレコードに対してはネストされた(複数の)要素や属性を返します:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: |
/// JSONの値に対するXML表現を作成する /// (ただしトップレベルの値がオブジェクトまたは配列の場合のみ機能する) let toXml(x:JsonValue) = // XML属性やXML要素を作成するためのヘルパ関数 let attr name value = XAttribute(XName.Get name, value) :> XObject let elem name (value:obj) = XElement(XName.Get name, value) :> XObject // 変換機能を実装している内部用再帰関数 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オブジェクトは(プリミティブであれば)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配列は <item> 要素のシーケンスになる | JsonValue.Array elements -> elements |> Array.map (fun item -> elem "item" (toXml item)) :> obj // 変換を実行して、結果をオブジェクトのシーケンスにキャストする // (意図しない入力に対しては失敗する可能性あり!) (toXml x) :?> XObject seq |
関連する記事
- F# Data: JSON パーサーおよびリーダー - JSONの値を動的に処理する方法についての説明があります。
- F# Data: JSON 型プロバイダー - 型安全な方法でJSONデータにアクセスする機能を持った F# 型プロバイダーについて説明しています。
- F# Data: XML 型プロバイダー - 型安全な方法でXMLデータにアクセスする機能を持った F# 型プロバイダーについて説明しています。
Full name: JsonToXml.fromXml
XML要素に対応するJSON表現を作成する
type XElement =
inherit XContainer
new : name:XName -> XElement + 4 overloads
member AncestorsAndSelf : unit -> IEnumerable<XElement> + 1 overload
member Attribute : name:XName -> XAttribute
member Attributes : unit -> IEnumerable<XAttribute> + 1 overload
member DescendantNodesAndSelf : unit -> IEnumerable<XNode>
member DescendantsAndSelf : unit -> IEnumerable<XElement> + 1 overload
member FirstAttribute : XAttribute
member GetDefaultNamespace : unit -> XNamespace
member GetNamespaceOfPrefix : prefix:string -> XNamespace
member GetPrefixOfNamespace : ns:XNamespace -> string
...
Full name: System.Xml.Linq.XElement
--------------------
XElement(name: XName) : unit
XElement(other: XElement) : unit
XElement(other: XStreamingElement) : unit
XElement(name: XName, content: obj) : unit
XElement(name: XName, params content: obj []) : unit
XElement.Attributes(name: XName) : System.Collections.Generic.IEnumerable<XAttribute>
| String of string
| Number of decimal
| Float of float
| Record of properties: (string * JsonValue) []
| Array of elements: JsonValue []
| Boolean of bool
| Null
member Post : uri:string * ?headers:(string * string) list -> HttpResponse
member Request : uri:string * ?httpMethod:string * ?headers:(string * string) list -> HttpResponse
member RequestAsync : uri:string * ?httpMethod:string * ?headers:(string * string) list -> Async<HttpResponse>
override ToString : unit -> string
member ToString : saveOptions:JsonSaveOptions -> string
static member AsyncLoad : uri:string * ?cultureInfo:CultureInfo -> Async<JsonValue>
static member Load : uri:string * ?cultureInfo:CultureInfo -> JsonValue
static member Load : reader:TextReader * ?cultureInfo:CultureInfo -> JsonValue
static member Load : stream:Stream * ?cultureInfo:CultureInfo -> JsonValue
static member Parse : text:string * ?cultureInfo:CultureInfo -> JsonValue
static member ParseMultiple : text:string * ?cultureInfo:CultureInfo -> seq<JsonValue>
static member ParseSample : text:string * ?cultureInfo:CultureInfo -> JsonValue
Full name: FSharp.Data.JsonValue
XContainer.Elements(name: XName) : System.Collections.Generic.IEnumerable<XElement>
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.groupBy
Full name: Microsoft.FSharp.Collections.Seq.map
Full name: Microsoft.FSharp.Collections.Seq.toList
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Array.append
Full name: Microsoft.FSharp.Collections.Array.ofList
Full name: Microsoft.FSharp.Collections.Array.ofSeq
Full name: JsonToXml.toXml
JSONの値に対するXML表現を作成する
(ただしトップレベルの値がオブジェクトまたは配列の場合のみ機能する)
type XAttribute =
inherit XObject
new : other:XAttribute -> XAttribute + 1 overload
member IsNamespaceDeclaration : bool
member Name : XName
member NextAttribute : XAttribute
member NodeType : XmlNodeType
member PreviousAttribute : XAttribute
member Remove : unit -> unit
member SetValue : value:obj -> unit
member ToString : unit -> string
member Value : string with get, set
...
Full name: System.Xml.Linq.XAttribute
--------------------
XAttribute(other: XAttribute) : unit
XAttribute(name: XName, value: obj) : unit
member Equals : obj:obj -> bool
member GetHashCode : unit -> int
member LocalName : string
member Namespace : XNamespace
member NamespaceName : string
member ToString : unit -> string
static member Get : expandedName:string -> XName + 1 overload
Full name: System.Xml.Linq.XName
XName.Get(localName: string, namespaceName: string) : XName
member AddAnnotation : annotation:obj -> unit
member Annotation<'T> : unit -> 'T + 1 overload
member Annotations<'T> : unit -> IEnumerable<'T> + 1 overload
member BaseUri : string
member Document : XDocument
member NodeType : XmlNodeType
member Parent : XElement
member RemoveAnnotations<'T> : unit -> unit + 1 overload
event Changed : EventHandler<XObjectChangeEventArgs>
event Changing : EventHandler<XObjectChangeEventArgs>
Full name: System.Xml.Linq.XObject
Full name: Microsoft.FSharp.Core.obj
Full name: Microsoft.FSharp.Collections.Array.map
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>