最近なんでもかんでもJSONですが、MongoDB/ElasticsearchにJSONを突っ込もうとして問題にぶつかりました。具体的には表題の通りで、 キーにDot(.)を含むものが上手く動かない という点です。現状は キーにDotを使うな、もしもデータファイルに入っていたらコンバートしろ ということなので、コンバートフィルタを書きました。`

どうして困るのか?

{
  "id": "hogehoge",
  "members": [
    "hisatoshi",
    "toshihisa"
  ],
  "detail": [
    {
      "name": "toshihisa"
    },
    {
      "name": "imaoka",
      "org": {
        "addr.name": "hogehoge",
        "tel.num": "fugafuga"
      }
    }
  ]
}

こういうJSONの場合、MongoDBやElasticsearchに突っ込んだ後、検索する場合は addr.name というキーは detail[1].org.addr.name と表現します。この場合 キーに含まれるDotが、階層を表すDotと競合してしまい、まともに動かないという現象が表れます。

例がよくなかった、途中で配列入っているの気にしないで。要は Object in Object な JSON

  • MongoDBの場合 Dotが含まれていようが、JSONインポートが出来ます。が、検索が出来ないので意味なし。
  • Elasticsearchの場合 インポートの段階で明確に蹴られます。

JSONキー置換フィルタの構想

最初は、正規表現の置換で良いかと思いましたが。JSONの中に JSON-textを含むような JSON-in-JSONのような構成をとられるとムダに破壊するかもと思い止めました。が、よく考えたら JSONのテキスト要素として JSONを表現する場合は でエスケープするので、正規表現だけでもいけますね、多分。。。

JSON吸って、pythonのオブジェクトにして、JSONで出す

多分正規表現のみでも行けそうと今、書いていて感じてますが、上記の方針で実装しました。 要は泥臭く実装するってことです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import json


def conv_key(obj):
    if isinstance(obj, dict):
        for org_key in obj.keys():
            new_key = org_key
            if '.' in org_key:
                new_key = org_key.translate({ord("."): ord("_")})
                obj[new_key] = obj.pop(org_key)

            conv_key(obj[new_key])

    elif isinstance(obj, list):
        for val in obj:
            conv_key(val)
    else:
        pass


def main():
    for line in sys.stdin:
        my_json = json.loads(line)
        conv_key(my_json)
        print json.dumps(my_json)


if __name__ == '__main__':
    main()

使い方

./conv_json_invalid_keys.py < hoge.json

実装の説明

  • 再起です。むちゃくちゃ深い JSONだとスタック足らないかもしれません。(そんなJSONで変換が必要とかどうかしている!)
  • JSON->Pythonの対応として、受けうる型は list , dict, 普通の値の3種類のはず
  • list は dictの listかもしれないので、そのままもう一回解析

気づき

  • CSVのように使うJSONは、1行1JSONで書く必要がある。醜いからといってjqで整形してしまうとインポート出来ない
  • type(obj) とかやって比較しようとしていたけど、そんなの時代遅れらしい、isinstance 使え!
  • tr相当の処理 string.translate も unicodeかstrかで違うらしい、この実装は unicode向け
  • json vs simplejson はほとんど変わらんらしいが、 enc/dec がそれぞれ対照的に速い・遅いらしい

元記事はこちら

JSON中にDot(.)が含まれたキー名を変換する