〜言語処理100本ノック No.20〜

## はじめに

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/#)という素敵教材をド素人が挑戦していく記事です。
今まで数回分まとめて書いていたのですが、今回は第3章1本目ということもあり、
JSON関連で色々躓いたのでその備忘録を書くためにNo.20だけにしました。

ちなみにこれまでのノック
[〜言語処理100本ノック No.00-04〜](https://qiita.com/ITNewcomer/items/a440b7bd01dcce194040)
[〜言語処理100本ノック No.05-09〜](https://qiita.com/ITNewcomer/items/60613e4ff0ea706c95e0)
[〜言語処理100本ノック No.10-14〜](https://qiita.com/ITNewcomer/items/4c3a6c58de87abe629f1)
[〜言語処理100本ノック No.15-19〜](https://qiita.com/ITNewcomer/items/2ba46a26408b1b180e5c)

## 環境

Mac OS X 10.14.5
Python 3.7.1

## 準備

Wikipediaの記事を以下のフォーマットで書き出したファイルを使います。
(詳しくはサイト参照)

* 1行に1記事の情報がJSON形式で格納される
* 各行には記事名が"title"キーに，記事本文が"text"キーの辞書オブジェクトに格納され，そのオブジェクトがJSON形式で書き出される
* ファイル全体はgzipで圧縮される

## 20. JSONデータの読み込み

> Wikipedia記事のJSONファイルを読み込み，「イギリス」に関する記事本文を表示せよ．問題21-29では，ここで抽出した記事本文に対して実行せよ．

### 実行コマンド

```python:20.py
import json
import gzip

with gzip.open('wiki_data/jawiki-country.json.gz') as f:
    for line in f:
        data = json.loads(line)
        if ('title', 'イギリス') in data.items():
            print(data)
```

## 躓いたあれこれ

### gzipファイル

最初はgzipファイルを展開して、JSONファイルとして処理していましたが、
調べていたらgzipのまま読み込んで処理できることが発覚...色々できるのね

```python
import gzip
with gzip.open('wiki_data/jawiki-country.json.gz') as f:
    for line in f:
<略>
```

### json.decoder.JSONDecodeError: Extra data: line 2 column 1

エラーが発生したJSON読み込みコードは以下です。

```python
with open('wiki_data/jawiki-country.json') as f:
    data = json.load(f)
```

今回のファイルは以下のように複数のJSONがつながっているようなデータでした。
なのでファイルごと読み込もうとしても、読めないと怒られてしまいました。

```json
{"text": "#転送 [[アイルランド共和国]]", "title": "アイルランド"}
{"text": "{{基礎情報 ...<長いので略>...[[Category:オセット]]", "title": "南オセチア"}
:
略
:
```

そのため、開いたファイルから一行ずつデータを読み込み
JSONとしてとりこんでいます。

```python
with open('wiki_data/jawiki-country.json', encoding='utf-8') as f:
    for line in f:
        data = json.loads(line)
```

### json.loadとjson.loadsの違い

上のエラーの際、JSONの読み込みに`json.load`を使っていますが、
最終的なコマンドでは`json.loads`を使っています。
違いは以下でした。

* load関数→ファイルオブジェクトからPythonオブジェクトに変換する
* loads関数→文字列からPythonオブジェクトに変換する

なので、今回は読み込むファイルが単一のJSONで作られていれば、読みこたということでした。

```json:test.json
{"text": "#転送 [[アイルランド共和国]]", "title": "アイルランド"}
```

```python
with open('test.json') as f:
    data = json.load(f)
```

一行ずつ読み込むときは文字列として読んでいるため、json.loads,で読み込むことができるというわけでした。

### JSONの表記が意外と厳格

これは調べていくうちに見つけた内容です。
Pythonは結構適当な(?)ところがあり、たとえばシングルクオートやダブルクオートがどっちでもいいように解釈してくれたりしてくれますが、
JSONデータの場合はそうも行かないようです。
以下が例です。

```bash
>>> json.loads('{"a": 1}')   # これはは読み込める
{'a': 1}
>>> json.loads("{'a': 1}")   # これはエラーとなる
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes
```

ファイルから直接読めば問題ないですが、
ファイル処理の中で、ダブルクオートがシングルクオートになっちゃったりするとエラーがでます。

```bash
>>> a = '{"a": 1}'
>>> a
'{"a": 1}'
>>> json.loads(a)   # これは読み込める
{'a': 1}　# シングルクオートになっている
>>> json_data = json.loads(a) # json_dataに一度格納
>>> type(json_data)
<class 'dict'>
>>> json.loads(json_data) # dictじゃ読めない
TypeError: the JSON object must be str, bytes or bytearray, not dict
>>> json.loads(str(json_data)) # strにして読み込むとシングルクオートで読めない
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
```

なので、JSONデータを扱うときは
json.daumpsを使ってJSON形式でちゃんと出力されるようにする必要があるみたいです。

```bash
>>> json_data
{'a': 1}
>>> json.dumps(json_data)
'{"a": 1}' # ダブルクオートで出力してくれる
```

## 最後に

第3章に突入しました。
JSONは最近扱い始めたので、ここでしっかり身につけたいです。