このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

もう一度 Google GData方式のXML-JSON変換の話

「Google GDataのXML-JSON変換、その使い方と使いどころ」で、Google GDataが定義しているXML-JSON変換を紹介しました。その方式を使うと、次のようなXMLデータは、すぐ下のJSONデータに変換されます。

XMLデータ:


<person gender="male">
<age>26</age>
<hobby>釣り</hobby>
<hobby>盆栽</hobby>
<name><family>坂東</family><given>トン吉</given></name>
<address type="home">東京都目黒区北目黒大目玉 0-100-99</address>
</person>

JSONデータ:


{
"person" : {
"gender" : "male",
"age" : {"$t" : "26"},
"hobby" : [{"$t" : "釣り"}, {"$t" : "盆栽"}],
"name" : {
"family" : {"$t": "坂東"},
"given": {"$t": "トン吉"}
},
"address" : {
"type" : "home",
"$t" : "東京都目黒区北目黒大目玉 0-100-99"
}
}
}

結果であるJSONデータには、"$t"という名前のプロパティが現れます。気持ち悪いけど、これは、XMLから変換する都合で避けられません。しかし、最初からJSONでデータ記述するなら、"$t"なんて変なプロパティは使わずに次のように書きますよね。


{
"person" : {
"gender" : "male",
"age" : 26,
"hobby" : ["釣り", "盆栽"],
"name" : {
"family" : "坂東",
"given": "トン吉"
},
"address" : {
"type" : "home",
"value" : "東京都目黒区北目黒大目玉 0-100-99"
}
}
}

2つのJSONデータを見比べると、{"$t" : "hello"} ←→ "hello" というタグイの変換をしていることになります。{"$t" : "hello"} は、XMLのDOMで考えるとテキストノードなので、文字列と同一視しても別にかまわないでしょう。しかし、{"$t" : "hello", "smile" : "true"} のように属性が付いていると、単なる文字列と同一視するわけにはいきません。そのままにするか、"$t"という変な名前をなんか適当な名前、例えば"value", "content", "text" とかに直します(プロパティ名のバッティングに気をつけて)。

{"$t" : "hello"} ←→ "hello" の変換(同一視)をCatyで採用しようと思ってるんですが、Kuwataさんはいかにもアドホックなルールに違和感を持っているようです。そこで、「そんなに変じゃないよ」という理屈(屁理屈?)をこねます。

属性付き文字列

文字列、例えば "hello" に属性が付いたデータを考えます。属性は、文字列の直後の丸括弧内に書くことにします。"hello"(smile=true) と、こんな感じ。属性は複数書けて、"hello"(smile=true, lang="en") とかもOK。属性がないなら、"hello"() ですが、空っぽの括弧は省略してもいいとすれば、"hello"() と "hello" は同じです。

属性付き文字列をJSON構文でエンコードするとして、{"$t" : "文字列", <属性並び>} のようにします。<属性並び>のところは、丸括弧内の属性をオブジェクトのプロパティにします。上に挙げた例をこの方法でJSONオブジェクトにすると:


{
"$t" : "hello",
"smile" : true
}

{
"$t" : "hello",
"smile" : true,
"lang" : "en"
}

{
"$t" : "hello",
}

型定義も書いておけば:


type AttributedString = {
"$t" : string,
// 属性はスカラーに限るとする
// ただし、nullは入れない
* : (number|string|boolean)?
};

「"hello"() と "hello" は同じ」と考えるなら、string → AttributedString という埋め込み(s |→ {"$t" : s})を使って、単なる文字列と“属性なしの属性付き文字列”を同一視します。これは、「1 と 1.0 が等しいかどうか? グロタンディーク構成で考えてみる」のときと同じ考え方です。

実際には、"hello" と {"$t": "hello"} のどちらを正規形と考えるか? とか、変換や同一視をどのタイミングで行うか? などの問題があります。

前例

"hello" と {"$t": "hello"} の同一視と似たことは既にCatyで行っています。

Catyの拡張JSONでは、どんなデータにもタグが付けられます。例えば、@yen 120 のように。タグ付きデータを普通のJSONエンコードするには次の方式を使っています。


{
"$tag" : "yen",
"$val" : 120
}

要するに、"$tag", "$val" というプロパティ名を特別扱いするという仕様です。現状では、"$tag"プロパティの省略を許してませんが、もし、{"$val" : 120} を許すなら、これはタグなしの値なので、単なる120と同じです。つまり、{"$val": 120} ←→ 120 と同一視することになります。

XMLへの変換に適したJSONデータ

GData方式と似た(ほとんど同じ)手順で、"$t"を使わない普通のJSONデータをXMLに変換しましょう。「普通」と言いましたが、どんなJSONデータでもOKというわけではないので、XMLに変換可能なJSONデータを特定します。そんなデータは、「XMLにするのに適切」ってことでXProperなデータとでも呼んでおきます。

JSONデータの部分集合を定義するときはCatyスキーマ言語を使うのが便利です。


type Scalar = (number|string|boolean); // nullはやめておく

type XProperObject = {
* : XProperValue?
};

type XProperValue = (Scalar|XProperArray|XProperObject);

type XProperArray = [(Scalar|XProperObject), (Scalar|XProperObject)*];

type XProperRoot = {
* : (Scalar|XProperObject)?
}(minProperties = 1, maxProperties = 1);

XML文書全体に対応するのがXProperRoot型で、これだけは特別扱いですが、あとは再帰的なデータ定義になっています。このスキーマ(型定義)に適合するJSONデータが与えられたときに、どうやってXMLに変換するかは次節で述べます。

JSON-to-XML変換の手順

手順を詳しく述べるのは面倒なので、原則と事例でお茶を濁します。

JSONデータをXMLデータに変換するときは、オブジェクトのプロパティ(名前と値のペア)ごとに考えます。プロパティの値がスカラーのとき/オブジェクトのとき/配列のときで扱いが異なりますが、基本となるのはスカラー値プロパティのときです。(名前 : スカラー値) というプロパティは次のいずれかに変換されます。

  1. 名前 = "スカラー値" というXML属性
  2. <名前>スカラー値</名前> というXML要素

変換のときは、コンテキストとなる親要素が指定されているので、変換結果である属性または要素は、親要素の属性または子要素として追加されます。問題は、XML属性に変換するのか、それともXML要素(子要素)に変換するのかの区別です。配列値プロパティがないときは、次のルールです。

  • 与えられたJSONデータのルート(トップレベル)のオブジェクトのプロパティのときはXML要素(ルート要素になる)に変換、それ以外ではXML属性に変換。

プロパティの値が配列のときは、名前と値のペアを複数個作ってから考えます。例えば、("greeting" : ["hello", {"with" : "smile"}]) なら、プロパティ名"greeting"を配列項目に分配して、("greeting" : "hello"), ("greeting" : {"with" : "smile"}) として考えます。このように配列から得られたプロパティのときは、XML要素に変換して処理します。この例では、<greeting>hello</greeting>, <greeting with="smile" /> と2つの子要素が得られます。

前に出した例をこの方式で変換してみましょう。

JSONデータ:


{
"person" : {
"gender" : "male",
"age" : 26,
"hobby" : ["釣り", "盆栽"],
"name" : {
"family" : "坂東",
"given": "トン吉"
},
"address" : {
"type" : "home",
"value" : "東京都目黒区北目黒大目玉 0-100-99"
}
}
}

XMLデータ:


<person
gender = "male"
age = "26"
>
<hobby>釣り</hobby>
<hobby>盆栽</hobby>
<name
family = "坂東"
given = "トン吉"
/>
<address
type = "home"
value = "東京都目黒区北目黒大目玉 0-100-99"
/>
</person>

結果であるXMLデータを、GData方式で再びJSONに変換すると、"$t"が現れます。しかし、"hello" ←→ {"$t": "hello"} の同一視をすれば、元に戻ったと言っていいでしょう。