ElasticsearchのmappingがJSON表記でわかりづらいからXMLで表現しようとした

# Elasticsearchのmappingフォーマットは見づらい

例えば次のようなJSONドキュメントがある場合

```json:familyタイプの1ドキュメント.json
{
    "person": {
        "name": "BIG BOSS",
        "sons": [{
            "name": "Solid Snake",
            "age": 32
        }, {
            "name": "Liquid Snake",
            "age": 32
        }]
    }
}
```

そのmappingはこんな感じになります

```json:familyタイプのmapping.json
{
    "person": {
        "properties": {
            "name": {
                "type": "string"
            },
            "sons": {
                "type": "nested",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "age": {
                        "type": "integer"
                    }
                }
            }
        }
    }
}
```

はい、非常にわかりづらいですね。
なぜか。propetiesのせいです。
ElasticsearchはJSONフォーマットでmappingを表現してるので、そのフィールドの属性（typeなど）と子要素（propeties）は両者ともオブジェクトの中に一つネストして格納するしかありません。そのためJSONドキュメントより階層が１つ深くなってしまい、mappingとJSONドキュメントの対応が直感的にわかりにくくなります。mappingの方がpropertiesのために階層が２倍深くなってしまうんですね（ついでに閉じカッコも必要となるので全体の行数も増えます）。

繰り返しになりますが、フィールドの属性と子要素がごっちゃになってしまうJSONの表現能力の限界が問題なのです。
ではJSONフォーマット以外でそのような用途に適したフォーマットはあるか。ありますよね、XMLという最適なフォーマットが。

# MappingをXMLで表現してみる

試しに上の例のmappingと等価な情報をXMLで表現したのが以下になります。

```xml:familyタイプのmapping.xml
<person>
    <name type="string" />
    <sons type="nested">
        <name type="string" />
        <age type="integer" />
    </sons>
</person>
```

以下の点が劇的に改善しました。

1. ネストの減少
  * JSONフォーマットだと最大6回のインデントがされてましたがXML表記ですと最大２回
2. 全体行数の減少
  * JSONフォーマットだと20行だったものがXMLでは7行
3. propetiesという不要な定数文字列の削除
  * 親子構造をデータフォーマットそのもので表すことができるようになったため、propetiesという定数文字列がなくせた

# 残った問題点

現状いくつか問題点がわかってます。

## 1. ネストされた属性はXMLで表現できない

XMLで属性へと変換されるproperties以外のプロパティの中にもしネストされてるものがあった場合、それはXMLでの属性として表現できないことです。

```json:XMLへ単純変換不可能なmappingの例.json
{
    "son": {
        "properties": {
            "name": {
                "type": "string",
                "norms": {
                    "enabled": false
                }
            }
        }
    }
}
```

この例でのnormsのように、属性の持つ情報がネストされてるとそれをそのままXMLで属性化することはできません。
[Elasticsearchのmapping仕様](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-core-types.html)上、このような属性は決まっており、自分の確認した限り2014/09/21のバージョン1.3.2時点ではネストする属性は以下になります。

* norms
* fielddata
* copy_to
* multiple fields

### 解決案

#### 1. 上の4つは例外ケースとして無視する(それらを使う場合はXMLフォーマットで書かない)

一時的に使う分にはそれでいいかもしれませんが、mappingの表現の標準としてXMLを使いたい場合は現実的ではありません。

#### 2. ネストされた属性は属性名のみエスケープして構造はそのまま維持する

ネストされてる場合は属性として表現するのが困難なのでXMLでもタグによる木構造として表現します。
ただし、他のタグと名前が衝突しないように属性名の頭へアンダースコアをつけます。
[Elasticsearchの仕様上](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-fields.html)アンダースコア始まりのフィールド名は予約されており、それらのどれかとネストされた属性の名前が衝突しない限りは安全です。

```json:ネストされた属性の例.json
{
    "son": {
        "properties": {
            "name": {
                "type": "string",
                "norms": {
                    "enabled": false
                }
            }
        }
    }
}
```

```xml:エスケープして木構造を維持する.xml
<son>
    <name type="string">
        <_norms enabled="false" />
    </name>
</son>
```

## 2. XMLの属性は文字列型以外持てない

XMLではデータの型というものが存在せず属性もすべて文字列であるため、JSONフォーマットでの文字列・真偽型・数値型の型情報が削ぎ落とされます。

```json:真偽型はXMLでどう表現する？
{
    "son": {
        "properties": {
            "name": {
                "type": "string",
                "store": true
            }
        }
    }
}
```

ここでのstoreのような値が文字列以外であるような属性はXMLで表現するのは難しいです。

### 解決案

#### 1. 型情報のprefixをつける

```xml:プレフィックス案
<son>
    <name
      type="string"
      store="boolean-true"
    />
</son>
```

非常に単純ですが問題も多いです。

1. ドキュメントがわかりにくくなる
2. prefixのルールがどのようなものかは決めの問題 = 覚えにくく忘れやすい
3. たまたまそのprefixのルールと合致するものが文字列として書かれる可能性がある
  * それを回避するためにエスケープすることもできるが、XML表記での仕様の複雑性を高める

#### 2. すべて文字列として単純変換し、その属性の型は属性名から推測

Elasticsearchの仕様上、属性の数と種類は限定されます。また属性の型は属性によって一意です。
よってXMLとして表記するときは単純にそのまま文字列へと変換し、XMLからJSONへと変換するときには用意した[属性名-型]の辞書を参照して適切な型へと変換します。

```xml:属性名から推測案
<son>
    <name
      type="string"
      store="true"
    />
</son>
```

問題点

1. 属性の種類を変換スクリプトで保持する必要がある = Elasticsearchが新しい属性を追加するたび、スクリプトへも追加してやる必要がある

# まとめ

本当はJSON⇔XMLの変換スクリプトを書こうとしたのですが、上記の複数の問題がわかったので現状では思考実験にとどまっています。
今の感触としては解決案の2と2をそれぞれ使うことで変換スクリプトは書けそうなのですが、まだ落とし穴がありそう。
ただ、仕事上で数百行のmappingを扱うにあたりあの冗長なmappingのフォーマットはなんとかしないといけないと思ってます。
なんらかアクションが取りたいなあ・・・。