ijsonでjsonを少しずつ読み込む

## はじめに
大容量のjsonをpythonのjsonライブラリで解析しようとしてメモリがすごく食われてメモリエラーになる事態になってしまいました。
そこでストリーム形式のパーサーを探してijsonを見つけましたが、資料が少ないためまとめます。
※簡単に使えるのでそもそも資料が不要かもしれません。

## 環境
 - python:3.6.5
 - ijson:2.4

## インストール
インストールは普通にpipでインストールします。

```
>pip install ijson
Collecting ijson
...
Successfully installed ijson-2.4
```

## 使用方法
### jsonの値の取得
ijsonのitems関数にjsonのファイルオブジェクトと取得したいキーを与えるとジェネレータが返却されます。
そのジェネレータからnextやループでjsonの値をとることができます。

``` python
import ijson
with open(jsonファイル, "r") as file_obj:
    ijson_generator = ijson.items(file_obj, "キー1.キー2")
    key2_value = next(ijson_generator)
```


### jsonのリストの取得
ほとんど値の取得と同様です。
リストの場合は取得したいキーの後にitemを追加してあげる必要があります。
返却されたジェネレータからループでjsonの値をとることができます。

``` python
import ijson
with open(jsonファイル, "r") as file_obj:
    ijson_generator = ijson.items(file_obj, "キー1.キー2.item")
    for value in ijson_generator:
        value
```

### json全体の解析
例えば、キーが可変のjsonを受け取る際などにキーを指定せずにjsonの解析をすることもできます。
prefixにはキーがeventには型、valueには値が格納されます。
以下の点が特殊なので気を付けてください。

- jsonのルート階層の情報を解析したときのprefixは空
- dictの始まりのeventはstart_map, valueはNone
- dictの終わりのeventはend_map, valueはNone
- dictのキーのeventはmap_key
- listの始まりのeventはstart_array, valueはNone
- listの終わりのeventはend_array, valueはNone
- listの要素のキーはキー.item

``` python
import ijson
with open(jsonファイル, "r") as file_obj:
    pet_parse = ijson.parse(file_obj)
    for prefix, event, value  in pet_parse:
        print("prefix:{}, event:{}, value:{}".format(prefix, event, value))
```

### 文字列の解析
今まではjsonファイルを開いて解析していましたが、WEBアプリケーションなどでは送信された文字列を解析したい場面があると思います。
そのような場合でも、ストリーム形式にしてあげることでijsonで解析ができます。

``` python
import ijson
import io
json_str = '{"pets": {"type": "dog","age": 5,"like": ["walking","eating","hamster"]},"dog": "bow"}'
pet_parser = ijson.parse(io.StringIO(json_str))
for prefix, event, value  in pet_parser:
    print("prefix:{}, event:{}, value:{}".format(prefix, event, value))
```


### 小ネタ
jsonの階層を無視してすべてのkeyを取りたい場合は、eventを利用して楽にできる
イコールの値を変えれば階層を無視して同じ型の情報を楽に集められる

``` python
with open(ファイル名, "r") as file:
    pet_parser = ijson.parse(file)
    map_keys = [value for pet_parse, event, value in pet_parser if event == 'map_key']
    print('map_keys:{}'.format(map_keys))
```


### 実例

使用するjson

``` json:animal.json
{
    "pets": {
        "type": "dog",
        "age": 5,
        "like": [
            "walking",
            "eating",
            "hamster"
        ]
    },
    "dog": "bow"
}
```

使用するpython

``` python:pyMod.py
import ijson
import io

print('------ get value ---------')
with open('animal.json', 'r') as file:
    pet_type = ijson.items(file, 'pets.type')
    print(next(pet_type))

print('------ get list ---------')
with open('animal.json', 'r') as file:
    pet_like = ijson.items(file, 'pets.like.item')
    for value in pet_like:
        print(value)

print('------ get all ---------')
with open('animal.json', 'r') as file:
    pet_parse = ijson.parse(file)
    for prefix, event, value  in pet_parse:
        print('prefix:{}, event:{}, value:{}'.format(prefix, event, value))

print('------ get all str ---------')
json_str = '{"pets": {"type": "dog","age": 5,"like": ["walking","eating","hamster"]},"dog": "bow"}'
pet_parser = ijson.parse(io.StringIO(json_str))
for prefix, event, value  in pet_parser:
    print('prefix:{}, event:{}, value:{}'.format(prefix, event, value))

print('------ get keys ---------')
with open('animal.json', 'r') as file:
    pet_parser = ijson.parse(file)
    map_keys = [value for pet_parse, event, value in pet_parser if event == 'map_key']
    print('map_keys:{}'.format(map_keys))

```

結果

```
------ get value ---------
dog
------ get list ---------
walking
eating
hamster
------ get all ---------
prefix:, event:start_map, value:None
prefix:, event:map_key, value:pets
prefix:pets, event:start_map, value:None
prefix:pets, event:map_key, value:type
prefix:pets.type, event:string, value:dog
prefix:pets, event:map_key, value:age
prefix:pets.age, event:number, value:5
prefix:pets, event:map_key, value:like
prefix:pets.like, event:start_array, value:None
prefix:pets.like.item, event:string, value:walking
prefix:pets.like.item, event:string, value:eating
prefix:pets.like.item, event:string, value:hamster
prefix:pets.like, event:end_array, value:None
prefix:pets, event:end_map, value:None
prefix:, event:map_key, value:dog
prefix:dog, event:string, value:bow
prefix:, event:end_map, value:None
------ get all str ---------
prefix:, event:start_map, value:None
prefix:, event:map_key, value:pets
prefix:pets, event:start_map, value:None
prefix:pets, event:map_key, value:type
prefix:pets.type, event:string, value:dog
prefix:pets, event:map_key, value:age
prefix:pets.age, event:number, value:5
prefix:pets, event:map_key, value:like
prefix:pets.like, event:start_array, value:None
prefix:pets.like.item, event:string, value:walking
prefix:pets.like.item, event:string, value:eating
prefix:pets.like.item, event:string, value:hamster
prefix:pets.like, event:end_array, value:None
prefix:pets, event:end_map, value:None
prefix:, event:map_key, value:dog
prefix:dog, event:string, value:bow
prefix:, event:end_map, value:None
------ get keys ---------
map_keys:['pets', 'type', 'age', 'like', 'dog']
```


## おわりに
jsonをストリーム形式で解析する方法を記載しましたが、キーの位置次第で一括で変換するよりも遅くなる場合があると思います。
そもそも論を言うとそこまで大きなjsonにするべきではない。少なくともdictの中にdictやlistを持たせるような複雑な構造にしてはいけないと思います。
慣れの問題もあるかもしれませんが、csvは情報が足りないと感じ、xmlは情報が過剰な気がしてjsonが程よい気がして、なんでもかんでもjsonにしたい気持ちもすごくわかります。
ならばせめてjsonの構造を簡単にして大容量にならないように工夫するのが一番かなと思っています。