PythonでJSONデータを操作する
開始以来、JSONはすぐに情報交換の事実上の標準になりました。 ここからそこにデータを転送する必要があるため、ここにいる可能性があります。 おそらく、APIを介して情報を収集しているか、document databaseにデータを保存しています。 いずれにせよ、あなたはJSONであなたの首に近づいています、そしてあなたはPythonにあなたの方法を持っています。
幸いなことに、これは非常に一般的なタスクであり、ほとんどの一般的なタスクと同様に、Pythonはほとんどうんざりするほど簡単です。 恐れることはありません、仲間のパイソン人とパイソン人。 これは簡単になります!
無料のPDFダウンロード: Python3チートシート
JSONの(非常に)簡単な履歴
それほど驚くことではありませんが、JavaScriptObjectNotationは、オブジェクトリテラル構文を処理するJavaScriptプログラミング言語のサブセットに触発されました。 全体を説明するnifty websiteがあります。 ただし、心配しないでください。JSONは言語にとらわれず、its own standardとして存在するため、この説明のために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は、ネストされたリストやオブジェクトだけでなく、文字列や数字などのプリミティブ型もサポートしています。
Wait, that looks like a Python dictionary!知ってるよね? この時点ではほぼ普遍的なオブジェクト表記ですが、UONが舌からうまく転がるとは思いません。 コメントで代替案について自由に議論してください。
こんにちは! 初めての野生のJSONとの出会いを生き延びました。 今、あなたはそれを飼いならす方法を学ぶ必要があります。
PythonはJSONをネイティブでサポートします!
Pythonには、JSONデータをエンコードおよびデコードするためのjson
という組み込みパッケージが付属しています。
ファイルの先頭にこの小さな男を投げるだけです:
import json
ちょっとした語彙
JSONをエンコードするプロセスは、通常、serializationと呼ばれます。 この用語は、データをseries of bytes(したがって、serial)に変換して、ネットワーク経由で保存または送信することを意味します。 marshalingという用語も聞こえるかもしれませんが、それはa whole other discussionです。 当然、deserializationは、JSON標準で保存または配信されたデータをデコードする相互プロセスです。
Yikes! That sounds pretty technical.間違いなく。 しかし実際には、ここで話しているのはreadingとwritingだけです。 次のように考えてください。encodingはディスクへのwritingデータ用であり、decodingはメモリへのreadingデータ用です。
JSONのシリアル化
コンピューターが大量の情報を処理した後はどうなりますか? データダンプを取得する必要があります。 したがって、json
ライブラリは、データをファイルに書き込むためのdump()
メソッドを公開します。 Python文字列に書き込むためのdumps()
メソッド(「dump-s」と発音)もあります。
単純なPythonオブジェクトは、かなり直感的な変換に従ってJSONに変換されます。
Python | JSON |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
簡単なシリアル化の例
メモリ内の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データをプログラムで引き続き使用したい場合は、ネイティブのPythonstr
オブジェクトに書き込むことができます。
json_string = json.dumps(data)
実際にディスクに書き込んでいないため、ファイルのようなオブジェクトは存在しないことに注意してください。 それ以外は、dumps()
はdump()
と同じです。
万歳! 赤ちゃんのJSONをいくつか作成したら、それをリリースして、大きく強力に成長させる準備ができています。
便利なキーワード引数
JSONは人間が簡単に読み取れることを意図していることを忘れないでください。しかし、読みやすい構文は、ひとまとめにしてしまうと十分ではありません。 さらに、おそらく私とは異なるプログラミングスタイルを使用しているので、好みに合わせてフォーマットされたコードを読む方が簡単かもしれません。
NOTE:
dump()
メソッドとdumps()
メソッドの両方が同じキーワード引数を使用します。
ほとんどの人が変更したい最初のオプションは空白です。 indent
キーワード引数を使用して、ネストされた構造のインデントサイズを指定できます。 上で定義したdata
を使用し、コンソールで次のコマンドを実行して、違いを確認してください。
>>>
>>> json.dumps(data)
>>> json.dumps(data, indent=4)
もう1つのフォーマットオプションは、separators
キーワード引数です。 デフォルトでは、これは区切り文字列(", ", ": ")
の2タプルですが、コンパクトJSONの一般的な代替手段は(",", ":")
です。 サンプルのJSONをもう一度見て、これらの区切り記号がどこで作用するかを確認してください。
sort_keys
のような他のものもありますが、それが何をするのかわかりません。 興味があれば、docsでリスト全体を見つけることができます。
JSONの逆シリアル化
素晴らしい、あなたは自分自身にワイルドなJSONをキャプチャしたようです! さあ、形に整えましょう。 json
ライブラリには、JSONでエンコードされたデータをPythonオブジェクトに変換するためのload()
とloads()
があります。
シリアル化と同様に、逆シリアル化用の簡単な変換テーブルがありますが、おそらく既にどのようなものかを推測できます。
JSON | Python |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
技術的には、この変換はシリアル化テーブルの完全な逆ではありません。 つまり、今すぐオブジェクトをエンコードし、後でもう一度デコードすると、まったく同じオブジェクトが返されない可能性があることを意味します。 テレポーテーションに少し似ていると思います。分子をここで分解し、それらを元に戻します。 私はまだ同じ人ですか?
現実には、おそらく一人の友達に何かを日本語に翻訳させ、もう一人の友達にそれを英語に翻訳してもらうことに似ているでしょう。 とにかく、最も簡単な例は、次のように、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)
>>> type(decoded_hand)
>>> 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を飼いならしたので、今ではあなたの管理下にあります。 しかし、その力で何をするかはあなた次第です。 餌を与えたり、育てたり、トリックを教えることさえできます。 私はあなたを信用していないということではありません...しかし、それをひもにつないでください、いいですか?
実世界の例(並べ替え)
紹介の例では、練習用の偽のJSONデータの優れたソースであるJSONPlaceholderを使用します。
まず、scratch.py
などのスクリプトファイルを作成します。 本当に止めることはできません。
JSONPlaceholderサービスにAPIリクエストを行う必要があるため、requests
パッケージを使用するだけで手間がかかります。 これらのインポートをファイルの上部に追加します。
import json
import requests
次に、TODOのリストを使用して作業します。これは、…通過儀式などです。
先に進み、/todos
エンドポイントのJSONPlaceholderAPIにリクエストを送信します。 requests
に慣れていない場合は、実際にはすべての作業を実行する便利なjson()
メソッドがありますが、json
ライブラリを使用してtext
を逆シリアル化する練習をすることができます。 )s応答オブジェクトの属性。 これは次のようになります。
response = requests.get("https://jsonplaceholder.typicode.com/todos")
todos = json.loads(response.text)
これが機能するとは思わない? ファイルを対話モードで実行し、自分でテストしてください。 そこにいる間に、todos
のタイプを確認します。 冒険心があるなら、リストの最初の10個ほどのアイテムを覗いてみてください。
>>>
>>> todos == response.json()
True
>>> type(todos)
>>> todos[:10]
...
ほら、私はあなたに嘘をつきませんが、あなたが懐疑的であることをうれしく思います。
What’s interactive mode?ああ、絶対に聞かないと思った! エディターと端末の間を常に行き来する方法を知っていますか? さて、私たち卑劣なPythoneersは、スクリプトを実行するときに
-i
インタラクティブフラグを使用します。 これは、スクリプトを実行してから、スクリプトのすべてのデータにアクセスできる対話型コマンドプロンプトを開くため、コードをテストするための非常に小さなトリックです。
よし、何か行動を起こそう。 ブラウザで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を完了した各ユーザーのcompletedTODOを含む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(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)
>>> json.dumps(z)
TypeError: Object of type 'complex' is not JSON serializable
Where do complex numbers come from?実数と虚数が互いに非常に愛し合うと、それらが足し合わされて、(当然のことながら)complexと呼ばれる数が生成されます。
カスタムタイプを操作するときに自問する良い質問はWhat is the minimum amount of information necessary to recreate this object?です。複素数の場合、必要なのは実数部と虚数部だけで、どちらも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
Why did we encode the complex number as a
tuple
?すばらしい質問です! それは確かに唯一の選択肢ではなく、必ずしも最善の選択肢でもありません。 実際、後ほど説明するように、オブジェクトを後でデコードしたい場合、これはあまり適切な表現ではありません。
他の一般的なアプローチは、標準の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
コンストラクターに渡す必要があります。 teleportationについての議論を思い出してください。 不足しているのは、metadata、つまりエンコードしているデータの種類に関する情報です。
あなたが本当に自問すべき質問はWhat is the minimum amount of information that is both necessary and sufficient to recreate this object?だと思います
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)
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
をオーバーライドすることもできますが、可能な限り軽量のソリューションを使用することをお勧めします。
全部できた!
おめでとうございます。これで、nefariousのPythonのニーズのすべてに対してJSONの強力なパワーを利用できるようになりました。
ここで扱った例は確かに不自然で単純すぎていますが、より一般的なタスクに適用できるワークフローを示しています。
-
json
パッケージをインポートします。 -
load()
またはloads()
でデータを読み取ります。 -
データを処理します。
-
変更されたデータを
dump()
またはdumps()
で書き込みます。
データがメモリに読み込まれた後のデータの処理は、ユースケースによって異なります。 一般的に、目標はソースからデータを収集し、有用な情報を抽出し、その情報を渡すか、記録を保持することです。
今日、あなたは旅に出ました:野生のJSONをキャプチャして飼いならし、夕食に間に合うように戻しました! 追加のボーナスとして、json
パッケージを学習すると、pickle
とmarshal
を簡単に学習できます。
あなたの将来のPythonicの努力のすべてで幸運を!