PythonでJSONデータを操作する

PythonでJSONデータを操作する

創業以来、https://en.wikipedia.org/wiki/JSON [JSON]は、情報交換の事実上の標準になりました。 ここからそこにデータを転送する必要があるため、ここにいる可能性があります。 おそらく、https://realpython.com/api-integration-in-python/[API]を介して情報を収集しているか、https://realpython.com/introduction-to-mongodb-and-pythonにデータを保存している可能性があります/[ドキュメントデータベース]。 いずれにせよ、あなたはJSONであなたの首に近づいています、そしてあなたはPythonにあなたの方法を持っています。

幸いなことに、これは非常に一般的なタスクであり、ほとんどの一般的なタスクと同様に、Pythonはほとんどうんざりするほど簡単です。 恐れることはありません、仲間のパイソン人とパイソン人。 これは簡単になります!

_ *では、JSONを使用してデータを保存および交換しますか?*はい、わかりました! コミュニティがデータの受け渡しに使用する標準化された形式にすぎません。 この種の作業に使用できる形式はJSONだけではありませんが、https://en.wikipedia.org/wiki/XML [XML]とhttp://yaml.org/[YAML]はおそらく同じ呼気で言及する価値のあるもののみ。 _

無料のPDFダウンロード: Python 3 Cheat Sheet

JSONの(非常に)簡単な履歴

それほど驚くことではないが、* J ava S cript O bject N * otationは、オブジェクトリテラル構文を扱うJavaScriptプログラミング言語のサブセットに触発されました。 全体を説明するhttps://www.json.org/[nifty website]があります。 ただし、JSONは長い間言語に依存せず、https://tools.ietf.org/html/rfc8259 [独自の標準]として存在しているため、この議論のためにJavaScriptを避けることができます。

最終的に、コミュニティ全体がJSONを採用したのは、人間と機械の両方が簡単に作成して理解できるからです。

見て、JSON!

準備をしなさい。 現実のJSONをお見せしようと思います-まるであなたが野生にいるのと同じように。 大丈夫です。JSONはCスタイル言語を使用している人なら誰でも読むことができるはずです。PythonはCスタイル言語です。

{
    "firstName": "Jane",
    "lastName": "Doe",
    "hobbies": ["running", "sky diving", "singing"],
    "age": 35,
    "children": [
        {
            "firstName": "Alice",
            "age": 6
        },
        {
            "firstName": "Bob",
            "age": 8
        }
    ]
}

ご覧のとおり、JSONは、ネストされたリストやオブジェクトだけでなく、文字列や数字などのプリミティブ型もサポートしています。

_ それはPython辞書のように見える! この時点ではほぼ普遍的なオブジェクト表記ですが、UONが舌からうまく転がるとは思いません。 コメントで代替案について自由に議論してください。 _

こんにちは! 初めての野生のJSONとの出会いを生き延びました。 今、あなたはそれを飼いならす方法を学ぶ必要があります。

PythonはJSONをネイティブでサポートします!

Pythonには、JSONデータをエンコードおよびデコードするためのhttps://docs.python.org/3/library/json.html [+ json +]という組み込みパッケージが付属しています。

ファイルの先頭にこの小さな男を投げるだけです:

import json

ちょっとした語彙

JSONをエンコードするプロセスは、通常*シリアライゼーション*と呼ばれます。 この用語は、データを_一連のバイト_(したがって_serial_)に変換して、ネットワークに保存または送信することを指します。 「マーシャリング」という用語を聞くこともありますが、それはhttps://stackoverflow.com/questions/770474/what-is-the-difference-between-serialization-and-marshaling [その他の議論]です。 当然、*逆シリアル化*は、JSON標準で保存または配信されたデータをデコードする相互プロセスです。

_ *うわぁ! それはかなり技術的に聞こえます。*間違いなく。 しかし、実際には、ここで話しているのは_reading_と_writing_だけです。 次のように考えてください:_encoding_はデータをディスクに書き込むためのものであり、_decoding_はデータをメモリに読み込むためのものです。 _

JSONのシリアル化

コンピューターが大量の情報を処理した後はどうなりますか? データダンプを取得する必要があります。 したがって、 `+ json `ライブラリは、ファイルにデータを書き込むための ` dump()`メソッドを公開します。 Python文字列に書き込むための ` dumps()+`メソッド(「dump-s」と発音)もあります。

単純なPythonオブジェクトは、かなり直感的な変換に従ってJSONに変換されます。

Python JSON

dict

object

list, tuple

array

str

string

int, long, float

number

True

true

False

false

None

null

簡単なシリアル化の例

メモリ内のPythonオブジェクトを使用して、次のように見えると想像してください。

data = {
    "president": {
        "name": "Zaphod Beeblebrox",
        "species": "Betelgeusian"
    }
}

この情報をディスクに保存することが重要であるため、ミッションはファイルに書き込むことです。

Pythonのコンテキストマネージャーを使用して、「+ data_file.json 」というファイルを作成し、書き込みモードで開くことができます。 (JSONファイルは便宜上「 .json +」拡張子で終わります。)

with open("data_file.json", "w") as write_file:
    json.dump(data, write_file)

`+ dump()+`は2つの位置引数を取ることに注意してください:(1)シリアル化されるデータオブジェクト、および(2)バイトが書き込まれるファイルのようなオブジェクト。

または、プログラムでこのシリアル化されたJSONデータを引き続き使用する傾向がある場合は、ネイティブPython `+ str +`オブジェクトに書き込むことができます。

json_string = json.dumps(data)

実際にディスクに書き込んでいないため、ファイルのようなオブジェクトは存在しないことに注意してください。 それ以外は、 `+ dumps()`は ` dump()+`と同じです。

万歳! 赤ちゃんのJSONをいくつか作成したら、それをリリースして、大きく強力に成長させる準備ができています。

便利なキーワード引数

JSONは人間が簡単に読み取れることを意図していることを忘れないでください。しかし、読みやすい構文は、ひとまとめにしてしまうと十分ではありません。 さらに、おそらく私とは異なるプログラミングスタイルを使用しているので、好みに合わせてフォーマットされたコードを読む方が簡単かもしれません。

_ 注意: `+ dump()`と ` dumps()+`の両方のメソッドは同じキーワード引数を使用します。 _

ほとんどの人が変更したい最初のオプションは空白です。 `+ indent `キーワード引数を使用して、ネストされた構造のインデントサイズを指定できます。 上で定義した ` data +`を使用し、コンソールで次のコマンドを実行して、自分の違いを確認してください。

>>>

>>> json.dumps(data)
>>> json.dumps(data, indent=4)

別のフォーマットオプションは、 `+ separators `キーワード引数です。 デフォルトでは、これは区切り文字列の2タプル `("、 "、": ")`ですが、コンパクトJSONの一般的な代替は `("、 "、": ")+`です。 サンプルのJSONをもう一度見て、これらの区切り記号がどこで作用するかを確認してください。

`+ sort_keys +`のような他のものがありますが、私はそれが何をするのか分かりません。 興味があれば、https://docs.python.org/3/library/json.html#basic-usage [docs]でリスト全体を見つけることができます。

JSONの逆シリアル化

素晴らしい、あなたは自分自身にワイルドなJSONをキャプチャしたようです! さあ、形に整えましょう。 `+ json `ライブラリには、JSONエンコードされたデータをPythonオブジェクトに変換するための ` load()`および ` loads()+`があります。

シリアル化と同様に、逆シリアル化用の簡単な変換テーブルがありますが、おそらく既にどのようなものかを推測できます。

JSON Python

object

dict

array

list

string

str

number (int)

int

number (real)

float

true

True

false

False

null

None

技術的には、この変換はシリアル化テーブルの完全な逆ではありません。 つまり、今すぐオブジェクトをエンコードし、後でもう一度デコードすると、まったく同じオブジェクトが返されない可能性があることを意味します。 テレポーテーションに少し似ていると思います。分子をここで分解し、それらを元に戻します。 私はまだ同じ人ですか?

現実には、おそらく一人の友達に何かを日本語に翻訳させ、もう一人の友達にそれを英語に翻訳してもらうことに似ているでしょう。 とにかく、最も簡単な例は、次のように、 `+ tuple `をエンコードし、デコード後に ` list +`を取得することです。

>>>

>>> blackjack_hand = (8, "Q")
>>> encoded_hand = json.dumps(blackjack_hand)
>>> decoded_hand = json.loads(encoded_hand)

>>> blackjack_hand == decoded_hand
False
>>> type(blackjack_hand)
<class 'tuple'>
>>> type(decoded_hand)
<class 'list'>
>>> blackjack_hand == tuple(decoded_hand)
True

簡単な逆シリアル化の例

今回は、メモリ上で操作したいデータがディスクに保存されていると想像してください。 コンテキストマネージャーは引き続き使用しますが、今回は既存の `+ data_file.json +`を読み取りモードで開きます。

with open("data_file.json", "r") as read_file:
    data = json.load(read_file)

ここでは非常に簡単ですが、このメソッドの結果は、変換テーブルから許可されたデータ型のいずれかを返す可能性があることに留意してください。 これは、これまでに見たことのないデータを読み込んでいる場合にのみ重要です。 ほとんどの場合、ルートオブジェクトは `+ dict `または ` list +`になります。

別のプログラムからJSONデータを取得した場合、またはPythonでJSON形式のデータの文字列を取得した場合は、文字列から自然に読み込まれる `+ loads()+`を使用して簡単に逆シリアル化できます。

json_string = """
{
    "researcher": {
        "name": "Ford Prefect",
        "species": "Betelgeusian",
        "relatives": [
            {
                "name": "Zaphod Beeblebrox",
                "species": "Betelgeusian"
            }
        ]
    }
}
"""
data = json.loads(json_string)

ほら! 野生のJSONを飼いならしたので、今ではあなたの管理下にあります。 しかし、その力で何をするかはあなた次第です。 餌を与えたり、育てたり、トリックを教えることさえできます。 私はあなたを信用していないということではありません…​しかし、それをひもにつないでください、いいですか?

実世界の例(並べ替え)

入門的な例として、https://jsonplaceholder.typicode.com/[JSONPlaceholder]を使用します。これは、練習用の偽のJSONデータの優れたソースです。

最初に、 `+ scratch.py​​ +`などのスクリプトファイルを作成します。 本当に止めることはできません。

JSONPlaceholderサービスにAPIリクエストを行う必要があるので、http://docs.python-requests.org/en/master/[+ requests +]パッケージを使用して手間をかけます。 これらのインポートをファイルの上部に追加します。

import json
import requests

次に、TODOのリストを使用して作業します。これは、…通過儀式などです。

先に進み、 `/todos +`エンドポイントのJSONPlaceholder APIにリクエストを行います。 ` requests `に慣れていない場合、実際にすべての作業を行う便利な ` json()`メソッドがありますが、 ` json `ライブラリを使用して ` text + `応答オブジェクトの属性。 これは次のようになります。

response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos = json.loads(response.text)

これが機能するとは思わない? ファイルを対話モードで実行し、自分でテストしてください。 その間、「+ todos +」のタイプを確認します。 冒険心があるなら、リストの最初の10個ほどのアイテムを覗いてみてください。

>>>

>>> todos == response.json()
True
>>> type(todos)
<class 'list'>
>>> todos[:10]
...

ほら、私はあなたに嘘をつきませんが、あなたが懐疑的であることをうれしく思います。

_ *インタラクティブモードとは何ですか?*ああ、絶対聞かないと思いました! エディターと端末の間を常に行き来する方法を知っていますか? さて、私たちの卑劣なPythoneerは、スクリプトを実行するときに `+ -i +`インタラクティブフラグを使用します。 これは、スクリプトを実行してから、スクリプトのすべてのデータにアクセスできる対話型コマンドプロンプトを開くため、コードをテストするための非常に小さなトリックです。 _

よし、何か行動を起こそう。 ブラウザでhttps://jsonplaceholder.typicode.com/todos[endpoint]にアクセスすると、データの構造を確認できますが、TODOのサンプルは次のとおりです。

{
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
}

複数のユーザーが存在し、それぞれに一意の `+ userId `があり、各タスクにはブール値の ` completed +`プロパティがあります。 どのユーザーがほとんどのタスクを完了したかを判断できますか?

# Map of userId to number of complete TODOs for that user
todos_by_user = {}

# Increment complete TODOs count for each user.
for todo in todos:
    if todo["completed"]:
        try:
            # Increment the existing user's count.
            todos_by_user[todo["userId"]] += 1
        except KeyError:
            # This user has not been seen. Set their count to 1.
            todos_by_user[todo["userId"]] = 1

# Create a sorted list of (userId, num_complete) pairs.
top_users = sorted(todos_by_user.items(),
                   key=lambda x: x[1], reverse=True)

# Get the maximum number of complete TODOs.
max_complete = top_users[0][1]

# Create a list of all users who have completed
# the maximum number of TODOs.
users = []
for user, num_complete in top_users:
    if num_complete < max_complete:
        break
    users.append(str(user))

max_users = " and ".join(users)

ええ、あなたの実装はより良いですが、ポイントは、JSONデータを通常のPythonオブジェクトとして操作できるようになったことです!

あなたについては知りませんが、スクリプトを再び対話的に実行すると、次の結果が得られます。

>>>

>>> s = "s" if len(users) > 1 else ""
>>> print(f"user{s} {max_users} completed {max_complete} TODOs")
users 5 and 10 completed 12 TODOs

それは素晴らしいことですが、JSONについて学ぶためにここに来ました。 最終タスクでは、TODOの最大数を完了した各ユーザーの*完了* TODOを含むJSONファイルを作成します。

必要なことは、 `+ todos `をフィルタリングし、結果のリストをファイルに書き込むことだけです。 独創性のために、出力ファイルを ` filtered_data_file.json +`と呼ぶことができます。 これについてはいくつかの方法がありますが、次のとおりです。

# Define a function to filter out completed TODOs
# of users with max completed TODOS.
def keep(todo):
    is_complete = todo["completed"]
    has_max_count = str(todo["userId"]) in users
    return is_complete and has_max_count

# Write filtered TODOs to file.
with open("filtered_data_file.json", "w") as data_file:
    filtered_todos = list(filter(keep, todos))
    json.dump(filtered_todos, data_file, indent=2)

完璧です。不要なデータをすべて取り除き、良いものを新しいファイルに保存しました! スクリプトを再度実行し、 `+ filtered_data_file.json `をチェックアウトして、すべてが機能したことを確認します。 実行時には、 ` scratch.py​​ +`と同じディレクトリにあります。

ここまで進んだところで、かなりホットなものを感じているのではないでしょうか。 生意気にならないでください。謙虚さは美徳です。 私はあなたに同意する傾向があります。 これまでのところ、順調に航海されていますが、この最後の行程のためにハッチを押し下げることをお勧めします。

カスタムPythonオブジェクトのエンコードとデコード

作業中のDungeons&Dragonsアプリの `+ Elf +`クラスをシリアル化しようとするとどうなりますか?

class Elf:
    def __init__(self, level, ability_scores=None):
        self.level = level
        self.ability_scores = {
            "str": 11, "dex": 12, "con": 10,
            "int": 16, "wis": 14, "cha": 13
        } if ability_scores is None else ability_scores
        self.hp = 10 + self.ability_scores["con"]

それほど驚くことではないが、Pythonは `+ Elf +`は_serializable_ではない(文句を言わずにElfに伝えようとしたことがあると知っている)と文句を言います。

>>>

>>> elf = Elf(level=4)
>>> json.dumps(elf)
TypeError: Object of type 'Elf' is not JSON serializable

`+ json +`モジュールはほとんどの組み込みPython型を処理できますが、デフォルトではカスタマイズされたデータ型をエンコードする方法を理解していません。 それは丸い穴に四角い釘をはめ込むようなものです。ノコギリと保護者の監督が必要です。

データ構造の簡素化

さて、問題はより複雑なデータ構造をどのように扱うかです。 手作業でJSONのエンコードとデコードを試みることもできますが、作業を節約できるもう少し賢いソリューションがあります。 カスタムデータ型からJSONに直接移行する代わりに、中間ステップを実行できます。

あなたがする必要があるのは、 `+ json `がすでに理解している組み込み型の観点からデータを表現することだけです。 基本的に、より複雑なオブジェクトをより単純な表現に変換し、 ` json +`モジュールがJSONに変換します。 それは数学の推移的な特性のようなものです。A= BかつB = Cの場合、A = Cです。

これを理解するには、複雑なオブジェクトを操作する必要があります。 任意のカスタムクラスを使用できますが、Pythonには複素数を表現するための `+ complex `と呼ばれる組み込み型があり、デフォルトではシリアル化できません。 したがって、これらの例のために、複雑なオブジェクトは ` complex +`オブジェクトになります。 まだ混乱している?

>>>

>>> z = 3 + 8j
>>> type(z)
<class 'complex'>
>>> json.dumps(z)
TypeError: Object of type 'complex' is not JSON serializable

_ *複素数はどこから来るのですか?*実数と虚数が非常に好きなとき、それらが足し合わされて(正当な理由で)https://www.mathsisfun.com/numbersと呼ばれる数を生成します/complex-numbers.html[_complex]。 __

カスタムタイプを使用する際に自問するのは良い質問です*このオブジェクトを再作成するために必要な最小情報量は?*複素数の場合は、実数部と虚数部のみを知る必要があります。 `+ complex +`オブジェクトの属性としてアクセス:

>>>

>>> z.real
3.0
>>> z.imag
8.0

同じ数値を `+ complex `コンストラクターに渡すだけで、 ` eq +`比較演算子を満たすのに十分です。

>>>

>>> complex(3, 8) == z
True

カスタムデータ型を必須コンポーネントに分解することは、シリアル化プロセスと逆シリアル化プロセスの両方にとって重要です。

カスタムタイプのエンコード

カスタムオブジェクトをJSONに変換するには、 `+ dump()`メソッドの ` default `パラメーターにエンコード関数を提供するだけです。 ` json +`モジュールは、ネイティブにシリアル化できないオブジェクトでこの関数を呼び出します。 以下は、練習に使用できる簡単なデコード機能です。

def encode_complex(z):
    if isinstance(z, complex):
        return (z.real, z.imag)
    else:
        type_name = z.__class__.__name__
        raise TypeError(f"Object of type '{type_name}' is not JSON serializable")

期待した種類のオブジェクトを取得できない場合は、「+ TypeError +」が発生することが予想されることに注意してください。 これにより、誤ってエルフをシリアル化することを回避できます。 複雑なオブジェクトを自分でエンコードしてみてください!

>>>

>>> json.dumps(9 + 5j, default=encode_complex)
'[9.0, 5.0]'
>>> json.dumps(elf, default=encode_complex)
TypeError: Object of type 'Elf' is not JSON serializable

_ *複素数を「タプル」としてエンコードしたのはなぜですか?*すばらしい質問です! それは確かに唯一の選択肢ではなく、必ずしも最善の選択肢でもありません。 実際、後ほど説明するように、オブジェクトを後でデコードしたい場合、これはあまり適切な表現ではありません。 _

他の一般的なアプローチは、標準の `+ JSONEncoder `をサブクラス化し、 ` default()+`メソッドをオーバーライドすることです:

class ComplexEncoder(json.JSONEncoder):
    def default(self, z):
        if isinstance(z, complex):
            return (z.real, z.imag)
        else:
            return super().default(z)

自分で `+ TypeError `を発生させる代わりに、単純に基本クラスに処理させることができます。 これは、 ` cls `パラメーターを介して ` dump()`メソッドで直接使用するか、エンコーダーのインスタンスを作成して ` encode()+`メソッドを呼び出すことで使用できます。

>>>

>>> json.dumps(2 + 5j, cls=ComplexEncoder)
'[2.0, 5.0]'

>>> encoder = ComplexEncoder()
>>> encoder.encode(3 + 6j)
'[3.0, 6.0]'

カスタムタイプのデコード

複素数の実数部と虚数部は絶対に必要ですが、実際にはオブジェクトを再作成するには十分ではありません。 これは、 `+ ComplexEncoder +`で複素数をエンコードしてから結果をデコードしようとすると発生します:

>>>

>>> complex_json = json.dumps(4 + 17j, cls=ComplexEncoder)
>>> json.loads(complex_json)
[4.0, 17.0]

返されるのはリストだけです。その複雑なオブジェクトが再度必要な場合は、値を `+ complex +`コンストラクターに渡す必要があります。 link:#deserializing-json [teleportation]に関する議論を思い出してください。 不足しているのは、メタデータ、またはエンコードしているデータの種類に関する情報です。

私はあなたが本当に自問すべき質問は*このオブジェクトを再作成するために必要な情報と必要な情報の両方の最小量は何ですか? *

`+ json `モジュールは、すべてのカスタムタイプがJSON標準で ` objects `として表現されることを想定しています。 さまざまな場合、今回は「 complex_data.json 」というJSONファイルを作成し、複素数を表す次の「 object +」を追加できます。

{
    "__complex__": true,
    "real": 42,
    "imag": 36
}

巧妙なビットを参照してください? その `" __ complex __ "`キーは、先ほど説明したメタデータです。 関連付けられている値が何であるかは実際には関係ありません。 この小さなハックを機能させるには、キーが存在することを確認するだけです。

def decode_complex(dct):
    if "__complex__" in dct:
        return complex(dct["real"], dct["imag"])
    return dct

`" __ complex __ "`が辞書にない場合は、オブジェクトを返すだけで、デフォルトのデコーダーで処理できます。

`+ load()`メソッドが ` object `を解析しようとするたびに、デフォルトのデコーダーがデータを処理する前に介入する機会が与えられます。 これを行うには、デコード関数を ` object_hook +`パラメーターに渡します。

今までと同じ種類のゲームをプレイします。

>>>

>>> with open("complex_data.json") as complex_data:
...     data = complex_data.read()
...     z = json.loads(data, object_hook=decode_complex)
...
>>> type(z)
<class 'complex'>

`+ object_hook `は ` dump()`メソッドの ` default +`パラメーターに対応するように感じるかもしれませんが、類推は実際にそこで始まり、終わります。

これは、1つのオブジェクトだけでは機能しません。 この複素数のリストを `+ complex_data.json +`に入れて、スクリプトを再度実行してください。

[
  {
    "__complex__":true,
    "real":42,
    "imag":36
  },
  {
    "__complex__":true,
    "real":64,
    "imag":11
  }
]

すべてうまくいけば、 `+ complex +`オブジェクトのリストが表示されます:

>>>

>>> with open("complex_data.json") as complex_data:
...     data = complex_data.read()
...     numbers = json.loads(data, object_hook=decode_complex)
...
>>> numbers
[(42+36j), (64+11j)]

「+ JSONDecoder 」をサブクラス化して「 object_hook +」をオーバーライドすることもできますが、可能な限り軽量なソリューションを使用することをお勧めします。

全部できた!

おめでとうございます。これで、JSONの強力な力を、[ラインスルー]* 悪名高い* Pythonのニーズのすべてに対応できるようになりました。

ここで扱った例は確かに不自然で単純すぎていますが、より一般的なタスクに適用できるワークフローを示しています。

  1. `+ json +`パッケージをインポートします。

  2. `+ load()`または ` loads()+`でデータを読み取ります。

  3. データを処理します。

  4. 変更されたデータを `+ dump()`または ` dumps()+`で書きます。

データがメモリに読み込まれた後のデータの処理は、ユースケースによって異なります。 一般的に、目標はソースからデータを収集し、有用な情報を抽出し、その情報を渡すか、記録を保持することです。

今日、あなたは旅に出ました:野生のJSONをキャプチャして飼いならし、夕食に間に合うように戻しました! 追加のボーナスとして、 + json +`パッケージを学習すると、https://docs.python.org/3/library/pickle.html [+ pickle `]およびhttps://docs.python.org/3が学習されます。/library/marshal.html [` marshal +`]スナップ。

あなたの将来のPythonicの努力のすべてで幸運を!