Python JSON オブジェクトで入れ子になったキーにアクセスする方法

PythonBeginner
オンラインで実践に進む

はじめに

Python の汎用性は、情報保存と交換に広く使用されている形式である JSON データとの連携にも及びます。JSON 構造は、Python の辞書やリストと同様に、単純なものから入れ子になった要素を持つ複雑なものまであります。このチュートリアルでは、実践的な演習を通して、Python で入れ子になった JSON 構造からデータにアクセスし、抽出する方法を学びます。

この実験(Lab)の終わりには、JSON オブジェクトを自信を持ってナビゲートし、深く入れ子になったキーにアクセスし、Python アプリケーションで入れ子になった配列を操作できるようになります。

Python での JSON の理解

JSON (JavaScript Object Notation) は、人間が読みやすく、機械が解析可能な軽量なデータ交換形式です。Python では、JSON オブジェクトは辞書として、JSON 配列はリストとして表現されます。

JSON データを探索するための簡単な Python スクリプトを作成することから始めましょう。

  1. WebIDE を開き、メニューから「File > New File」をクリックして新しいファイルを作成します。

  2. ファイルを /home/labex/project/json_practice ディレクトリに basic_json.py として保存します。

  3. basic_json.py に次のコードを追加します。

import json

## A simple JSON object (represented as a Python dictionary)
person = {
    "name": "John Doe",
    "age": 30,
    "email": "john.doe@example.com",
    "is_employed": True,
    "hobbies": ["reading", "hiking", "coding"]
}

## Convert Python dictionary to JSON string
json_string = json.dumps(person, indent=2)
print("JSON as string:")
print(json_string)
print("\n" + "-" * 50 + "\n")

## Convert JSON string back to Python dictionary
parsed_json = json.loads(json_string)
print("Python dictionary:")
print(parsed_json)
print("\n" + "-" * 50 + "\n")

## Accessing basic elements
print("Basic access examples:")
print(f"Name: {parsed_json['name']}")
print(f"Age: {parsed_json['age']}")
print(f"First hobby: {parsed_json['hobbies'][0]}")
  1. WebIDE でターミナルを開き、以下を実行してスクリプトを実行します。
cd /home/labex/project/json_practice
python3 basic_json.py

次のような出力が表示されるはずです。

JSON as string:
{
  "name": "John Doe",
  "age": 30,
  "email": "john.doe@example.com",
  "is_employed": true,
  "hobbies": [
    "reading",
    "hiking",
    "coding"
  ]
}

--------------------------------------------------

Python dictionary:
{'name': 'John Doe', 'age': 30, 'email': 'john.doe@example.com', 'is_employed': True, 'hobbies': ['reading', 'hiking', 'coding']}

--------------------------------------------------

Basic access examples:
Name: John Doe
Age: 30
First hobby: reading

コードの理解

  • json.dumps() は、Python オブジェクトを JSON 形式の文字列に変換します。
  • json.loads() は、JSON 文字列を解析し、Python オブジェクトに変換します。
  • JSON オブジェクト内の要素にアクセスするには、辞書構文を使用します:object_name['key']
  • JSON 配列内の要素にアクセスするには、リストインデックスを使用します:array_name[index]

この例では、Python で JSON を操作するための基本的な操作を示しています。次のステップでは、入れ子になった JSON 構造へのアクセス方法を探索します。

入れ子になった辞書キーへのアクセス

JSON オブジェクトには、入れ子になった構造がよく含まれています。Python では、複数の角括弧を使用するか、辞書キーを連鎖させることで、入れ子になったデータにアクセスできます。

入れ子になった JSON オブジェクトを探索するための新しいスクリプトを作成しましょう。

  1. WebIDE で、新しいファイルを作成し、/home/labex/project/json_practice ディレクトリに nested_dict.py として保存します。

  2. nested_dict.py に次のコードを追加します。

import json

## JSON with nested object
user_data = {
    "name": "John Doe",
    "age": 30,
    "contact": {
        "email": "john.doe@example.com",
        "phone": "555-1234",
        "address": {
            "street": "123 Main St",
            "city": "Anytown",
            "state": "CA",
            "zip": "12345"
        }
    },
    "preferences": {
        "theme": "dark",
        "notifications": True
    }
}

## Let's prettify and print the JSON structure
print("Full JSON structure:")
print(json.dumps(user_data, indent=2))
print("\n" + "-" * 50 + "\n")

## Accessing nested elements
print("Accessing nested elements:")
print(f"Email: {user_data['contact']['email']}")
print(f"City: {user_data['contact']['address']['city']}")
print(f"Theme: {user_data['preferences']['theme']}")
print("\n" + "-" * 50 + "\n")

## Safe access with get() method
print("Safe access with get():")
## get() returns None if key doesn't exist, or a default value if specified
phone = user_data.get('contact', {}).get('phone', 'Not available')
country = user_data.get('contact', {}).get('address', {}).get('country', 'Not specified')

print(f"Phone: {phone}")
print(f"Country: {country}")  ## This key doesn't exist but won't cause an error
  1. ターミナルでスクリプトを実行します。
cd /home/labex/project/json_practice
python3 nested_dict.py

次のような出力が表示されるはずです。

Full JSON structure:
{
  "name": "John Doe",
  "age": 30,
  "contact": {
    "email": "john.doe@example.com",
    "phone": "555-1234",
    "address": {
      "street": "123 Main St",
      "city": "Anytown",
      "state": "CA",
      "zip": "12345"
    }
  },
  "preferences": {
    "theme": "dark",
    "notifications": true
  }
}

--------------------------------------------------

Accessing nested elements:
Email: john.doe@example.com
City: Anytown
Theme: dark

--------------------------------------------------

Safe access with get():
Phone: 555-1234
Country: Not specified

入れ子になったアクセスについて

入れ子になった JSON 構造を操作する場合、角括弧を使用してキーを連鎖させることで、入れ子になった要素にアクセスできます。

## Syntax for nested access
value = json_data['key1']['key2']['key3']

ただし、このアプローチでは、チェーン内のキーが存在しない場合、エラーが発生する可能性があります。より安全な方法は、get() 関数を使用することです。これにより、キーが見つからない場合にデフォルト値を指定できます。

## Safe access with get() method
value = json_data.get('key1', {}).get('key2', {}).get('key3', 'default_value')

この安全なアクセスパターンは、API レスポンスや、構造が必ずしも一貫していない他の外部 JSON データを使用する場合に特に有効です。

JSON での入れ子になった配列の操作

JSON データには、他のオブジェクトや配列を含む可能性のある配列 (Python ではリスト) が含まれることがよくあります。入れ子になった配列内の要素にアクセスする方法を調べてみましょう。

  1. WebIDE で、新しいファイルを作成し、/home/labex/project/json_practice ディレクトリに nested_arrays.py として保存します。

  2. nested_arrays.py に次のコードを追加します。

import json

## JSON with nested arrays
company_data = {
    "name": "Tech Innovations Inc",
    "founded": 2010,
    "departments": [
        {
            "name": "Engineering",
            "employees": [
                {"name": "Alice Johnson", "role": "Software Engineer", "skills": ["Python", "JavaScript", "AWS"]},
                {"name": "Bob Smith", "role": "DevOps Engineer", "skills": ["Docker", "Kubernetes", "Linux"]}
            ]
        },
        {
            "name": "Marketing",
            "employees": [
                {"name": "Carol Williams", "role": "Marketing Manager", "skills": ["SEO", "Content Strategy"]},
                {"name": "Dave Brown", "role": "Social Media Specialist", "skills": ["Facebook Ads", "Instagram"]}
            ]
        }
    ],
    "locations": ["San Francisco", "New York", "London"]
}

## Print the JSON structure
print("Company Data:")
print(json.dumps(company_data, indent=2))
print("\n" + "-" * 50 + "\n")

## Accessing elements in arrays
print("Accessing array elements:")
print(f"First location: {company_data['locations'][0]}")
print(f"Number of departments: {len(company_data['departments'])}")
print(f"First department name: {company_data['departments'][0]['name']}")
print("\n" + "-" * 50 + "\n")

## Iterating through nested arrays
print("All employees and their skills:")
for department in company_data['departments']:
    dept_name = department['name']
    print(f"\nDepartment: {dept_name}")
    print("-" * 20)

    for employee in department['employees']:
        print(f"  {employee['name']} ({employee['role']})")
        print(f"  Skills: {', '.join(employee['skills'])}")
        print()

## Finding specific data in nested arrays
print("-" * 50 + "\n")
print("Finding employees with Python skills:")

for department in company_data['departments']:
    for employee in department['employees']:
        if "Python" in employee['skills']:
            print(f"  {employee['name']} in {department['name']} department")
  1. ターミナルでスクリプトを実行します。
cd /home/labex/project/json_practice
python3 nested_arrays.py

次のような出力が表示されるはずです。

Company Data:
{
  "name": "Tech Innovations Inc",
  "founded": 2010,
  "departments": [
    {
      "name": "Engineering",
      "employees": [
        {
          "name": "Alice Johnson",
          "role": "Software Engineer",
          "skills": [
            "Python",
            "JavaScript",
            "AWS"
          ]
        },
        {
          "name": "Bob Smith",
          "role": "DevOps Engineer",
          "skills": [
            "Docker",
            "Kubernetes",
            "Linux"
          ]
        }
      ]
    },
    {
      "name": "Marketing",
      "employees": [
        {
          "name": "Carol Williams",
          "role": "Marketing Manager",
          "skills": [
            "SEO",
            "Content Strategy"
          ]
        },
        {
          "name": "Dave Brown",
          "role": "Social Media Specialist",
          "skills": [
            "Facebook Ads",
            "Instagram"
          ]
        }
      ]
    }
  ],
  "locations": [
    "San Francisco",
    "New York",
    "London"
  ]
}

--------------------------------------------------

Accessing array elements:
First location: San Francisco
Number of departments: 2
First department name: Engineering

--------------------------------------------------

All employees and their skills:

Department: Engineering
--------------------
  Alice Johnson (Software Engineer)
  Skills: Python, JavaScript, AWS

  Bob Smith (DevOps Engineer)
  Skills: Docker, Kubernetes, Linux


Department: Marketing
--------------------
  Carol Williams (Marketing Manager)
  Skills: SEO, Content Strategy

  Dave Brown (Social Media Specialist)
  Skills: Facebook Ads, Instagram

--------------------------------------------------

Finding employees with Python skills:
  Alice Johnson in Engineering department

入れ子になった配列へのアクセスについて

JSON で入れ子になった配列を操作するには、次の組み合わせが必要です。

  1. インデックスアクセス: 角括弧とインデックスを使用して、特定の配列要素にアクセスします (array[0])
  2. 入れ子になったプロパティアクセス: 辞書キーと配列インデックスを連鎖させます (data['departments'][0]['employees'])
  3. 反復処理: ループを使用して、配列内の複数の項目を処理します

入れ子になった配列を操作するための最も一般的なパターンは、入れ子になった for ループを使用することです。

for outer_item in json_data['outer_array']:
    for inner_item in outer_item['inner_array']:
        ## Process the inner_item
        print(inner_item['property'])

このアプローチにより、複雑な入れ子構造を走査し、必要な特定のデータを抽出できます。

欠落キーの処理とエラー防止

複雑な JSON 構造、特に API などの外部ソースからの JSON 構造を扱う場合、キーが欠落している場合に発生する可能性のあるエラーを処理することが重要です。入れ子になった JSON データに安全にアクセスするためのテクニックを調べてみましょう。

  1. WebIDE で、新しいファイルを作成し、/home/labex/project/json_practice ディレクトリに error_handling.py として保存します。

  2. error_handling.py に次のコードを追加します。

import json

## JSON with inconsistent structure
api_response = {
    "status": "success",
    "data": {
        "users": [
            {
                "id": 1,
                "name": "John Doe",
                "contact": {
                    "email": "john.doe@example.com",
                    "phone": "555-1234"
                },
                "roles": ["admin", "user"]
            },
            {
                "id": 2,
                "name": "Jane Smith",
                ## Missing contact info
                "roles": ["user"]
            },
            {
                "id": 3,
                "name": "Bob Johnson",
                "contact": {
                    ## Only has email, no phone
                    "email": "bob.johnson@example.com"
                }
                ## Missing roles
            }
        ]
    }
}

print("API Response Structure:")
print(json.dumps(api_response, indent=2))
print("\n" + "-" * 50 + "\n")

## Approach 1: Using try-except blocks
print("Method 1: Using try-except blocks")
print("-" * 30)

for user in api_response["data"]["users"]:
    print(f"User: {user['name']}")

    ## Get email
    try:
        email = user['contact']['email']
        print(f"  Email: {email}")
    except (KeyError, TypeError):
        print("  Email: Not available")

    ## Get phone
    try:
        phone = user['contact']['phone']
        print(f"  Phone: {phone}")
    except (KeyError, TypeError):
        print("  Phone: Not available")

    ## Get roles
    try:
        roles = ", ".join(user['roles'])
        print(f"  Roles: {roles}")
    except (KeyError, TypeError):
        print("  Roles: None assigned")

    print()

## Approach 2: Using get() method with defaults
print("\n" + "-" * 50 + "\n")
print("Method 2: Using get() method with defaults")
print("-" * 30)

for user in api_response["data"]["users"]:
    print(f"User: {user['name']}")

    ## Get contact info with nested get() calls
    contact = user.get('contact', {})
    email = contact.get('email', 'Not available')
    phone = contact.get('phone', 'Not available')

    print(f"  Email: {email}")
    print(f"  Phone: {phone}")

    ## Get roles with default empty list
    roles = user.get('roles', [])
    roles_str = ", ".join(roles) if roles else "None assigned"
    print(f"  Roles: {roles_str}")

    print()
  1. ターミナルでスクリプトを実行します。
cd /home/labex/project/json_practice
python3 error_handling.py

次のような出力が表示されるはずです。

API Response Structure:
{
  "status": "success",
  "data": {
    "users": [
      {
        "id": 1,
        "name": "John Doe",
        "contact": {
          "email": "john.doe@example.com",
          "phone": "555-1234"
        },
        "roles": [
          "admin",
          "user"
        ]
      },
      {
        "id": 2,
        "name": "Jane Smith",
        "roles": [
          "user"
        ]
      },
      {
        "id": 3,
        "name": "Bob Johnson",
        "contact": {
          "email": "bob.johnson@example.com"
        }
      }
    ]
  }
}

--------------------------------------------------

Method 1: Using try-except blocks
------------------------------
User: John Doe
  Email: john.doe@example.com
  Phone: 555-1234
  Roles: admin, user

User: Jane Smith
  Email: Not available
  Phone: Not available
  Roles: user

User: Bob Johnson
  Email: bob.johnson@example.com
  Phone: Not available
  Roles: None assigned


--------------------------------------------------

Method 2: Using get() method with defaults
------------------------------
User: John Doe
  Email: john.doe@example.com
  Phone: 555-1234
  Roles: admin, user

User: Jane Smith
  Email: Not available
  Phone: Not available
  Roles: user

User: Bob Johnson
  Email: bob.johnson@example.com
  Phone: Not available
  Roles: None assigned

エラー処理テクニックについて

この例では、入れ子になった JSON データに安全にアクセスするための 2 つの主なアプローチを示しています。

  1. Try-Except ブロック

    • 潜在的に危険なアクセスを try-except ブロックでラップします
    • KeyError (欠落している辞書キー) と TypeError (辞書以外のキーへのアクセスを試行) をキャッチします
    • エラーが発生した場合にフォールバック値を指定します
  2. Chained get() メソッド

    • デフォルト値を 2 番目の引数として受け取る辞書の get() メソッドを使用します
    • 入れ子になった構造に対して複数の get() 呼び出しを連鎖させます
    • 例外処理なしで、よりクリーンなコードを提供します

入れ子になった JSON 構造を扱う場合、get() メソッドのアプローチは、その可読性と簡潔さから一般的に推奨されます。これにより、ネストの各レベルでデフォルト値を指定できます。

## Safe nested access pattern
value = data.get('level1', {}).get('level2', {}).get('level3', 'default_value')

これらのエラー処理テクニックを使用すると、さまざまなソースからの JSON データを扱う際に、コードの堅牢性が向上します。

実践的な演習:JSON データ抽出器の構築

複雑な JSON 構造から特定の情報を抽出するプログラムを作成して、あなたの知識を実践してみましょう。これは、API から JSON データを受信し、それを処理する必要がある現実世界のシナリオを表す可能性があります。

  1. WebIDE で、新しいファイルを作成し、/home/labex/project/json_practice ディレクトリに json_extractor.py として保存します。

  2. json_extractor.py に次のコードを追加します。

import json

## A complex nested JSON structure (e.g., from a weather API)
weather_data = {
    "location": {
        "name": "New York",
        "region": "New York",
        "country": "United States of America",
        "lat": 40.71,
        "lon": -74.01,
        "timezone": "America/New_York"
    },
    "current": {
        "temp_c": 22.0,
        "temp_f": 71.6,
        "condition": {
            "text": "Partly cloudy",
            "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png",
            "code": 1003
        },
        "wind_mph": 6.9,
        "wind_kph": 11.2,
        "wind_dir": "ENE",
        "humidity": 65,
        "cloud": 75,
        "feelslike_c": 22.0,
        "feelslike_f": 71.6
    },
    "forecast": {
        "forecastday": [
            {
                "date": "2023-09-20",
                "day": {
                    "maxtemp_c": 24.3,
                    "maxtemp_f": 75.7,
                    "mintemp_c": 18.6,
                    "mintemp_f": 65.5,
                    "condition": {
                        "text": "Patchy rain possible",
                        "icon": "//cdn.weatherapi.com/weather/64x64/day/176.png",
                        "code": 1063
                    },
                    "daily_chance_of_rain": 85
                },
                "astro": {
                    "sunrise": "06:41 AM",
                    "sunset": "07:01 PM",
                    "moonrise": "10:15 AM",
                    "moonset": "08:52 PM"
                },
                "hour": [
                    {
                        "time": "2023-09-20 00:00",
                        "temp_c": 20.1,
                        "condition": {
                            "text": "Clear",
                            "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png",
                            "code": 1000
                        },
                        "chance_of_rain": 0
                    },
                    {
                        "time": "2023-09-20 12:00",
                        "temp_c": 23.9,
                        "condition": {
                            "text": "Overcast",
                            "icon": "//cdn.weatherapi.com/weather/64x64/day/122.png",
                            "code": 1009
                        },
                        "chance_of_rain": 20
                    }
                ]
            },
            {
                "date": "2023-09-21",
                "day": {
                    "maxtemp_c": 21.2,
                    "maxtemp_f": 70.2,
                    "mintemp_c": 16.7,
                    "mintemp_f": 62.1,
                    "condition": {
                        "text": "Heavy rain",
                        "icon": "//cdn.weatherapi.com/weather/64x64/day/308.png",
                        "code": 1195
                    },
                    "daily_chance_of_rain": 92
                },
                "astro": {
                    "sunrise": "06:42 AM",
                    "sunset": "06:59 PM",
                    "moonrise": "11:30 AM",
                    "moonset": "09:15 PM"
                }
            }
        ]
    }
}

def extract_weather_summary(data):
    """
    Extract and format a weather summary from the provided data.
    """
    try:
        ## Location information
        location = data.get("location", {})
        location_name = location.get("name", "Unknown")
        country = location.get("country", "Unknown")

        ## Current weather
        current = data.get("current", {})
        temp_c = current.get("temp_c", "N/A")
        temp_f = current.get("temp_f", "N/A")
        condition = current.get("condition", {}).get("text", "Unknown")
        humidity = current.get("humidity", "N/A")

        ## Forecast
        forecast_days = data.get("forecast", {}).get("forecastday", [])

        ## Build summary string
        summary = f"Weather Summary for {location_name}, {country}\n"
        summary += f"==================================================\n\n"
        summary += f"Current Conditions:\n"
        summary += f"  Temperature: {temp_c}°C ({temp_f}°F)\n"
        summary += f"  Condition: {condition}\n"
        summary += f"  Humidity: {humidity}%\n\n"

        if forecast_days:
            summary += "Forecast:\n"
            for day_data in forecast_days:
                date = day_data.get("date", "Unknown date")
                day = day_data.get("day", {})
                max_temp = day.get("maxtemp_c", "N/A")
                min_temp = day.get("mintemp_c", "N/A")
                condition = day.get("condition", {}).get("text", "Unknown")
                rain_chance = day.get("daily_chance_of_rain", "N/A")

                summary += f"  {date}:\n"
                summary += f"    High: {max_temp}°C, Low: {min_temp}°C\n"
                summary += f"    Condition: {condition}\n"
                summary += f"    Chance of Rain: {rain_chance}%\n"

                ## Get sunrise and sunset times if available
                astro = day_data.get("astro", {})
                if astro:
                    sunrise = astro.get("sunrise", "N/A")
                    sunset = astro.get("sunset", "N/A")
                    summary += f"    Sunrise: {sunrise}, Sunset: {sunset}\n"

                summary += "\n"

        return summary

    except Exception as e:
        return f"Error extracting weather data: {str(e)}"

## Print the full JSON data
print("Original Weather Data:")
print(json.dumps(weather_data, indent=2))
print("\n" + "-" * 60 + "\n")

## Extract and print the weather summary
weather_summary = extract_weather_summary(weather_data)
print(weather_summary)

## Save the summary to a file
with open("weather_summary.txt", "w") as file:
    file.write(weather_summary)

print("\nWeather summary has been saved to 'weather_summary.txt'")
  1. ターミナルでスクリプトを実行します。
cd /home/labex/project/json_practice
python3 json_extractor.py
  1. スクリプトを実行した後、生成されたサマリーファイルを表示できます。
cat weather_summary.txt

複雑な JSON 構造から抽出された、フォーマットされた天気概要が表示されます。

JSON 抽出器について

この実践的な演習では、入れ子になった JSON データを扱うためのいくつかの重要な概念を示します。

  1. 安全なデータ抽出

    • 欠落しているキーを処理するために、デフォルト値を持つ get() メソッドを使用する
    • 連鎖した get() 呼び出しで入れ子になったデータにアクセスする
    • try-except ブロックによるエラー処理
  2. データ変換

    • JSON データを人間が読める形式に変換する
    • 複数の項目を処理するために、入れ子になった配列を反復処理する
    • 複雑な構造から関連情報のみを抽出する
  3. 防御的プログラミング

    • 潜在的な問題を予測して処理する
    • データが欠落している場合にデフォルト値を指定する
    • プログラムのクラッシュを防ぐために例外をキャッチする

このデータ抽出、変換、および提示のパターンは、次のような多くの現実世界のアプリケーションで一般的です。

  • API レスポンスの処理
  • データからのレポートの生成
  • ユーザー向けの情報フィルタリングと表示

これらのパターンに従うことで、あらゆる複雑さの JSON データを確実に操作できます。

まとめ

この Lab では、Python で入れ子になった JSON 構造を操作する方法を学びました。

  1. 基本的な JSON 処理 - json.dumps()json.loads() を使用して、Python オブジェクトと JSON 文字列の間で変換します。

  2. 入れ子になった辞書キーへのアクセス - 連鎖した角括弧表記を使用して、JSON オブジェクト内の深く入れ子になった値にアクセスします。

  3. 入れ子になった配列の操作 - インデックスと反復処理を使用して、JSON 構造内の配列をナビゲートし、そこからデータを抽出します。

  4. エラー処理テクニック - try-except ブロックと get() メソッドを使用して、欠落しているキーを処理するための安全なアクセスパターンを実装します。

  5. 実践的なデータ抽出 - 複雑な JSON 構造からデータを抽出、変換、および提示する完全なアプリケーションを構築します。

これらのスキルは、API、設定ファイル、およびその他のデータ交換シナリオからのデータを扱うために不可欠です。これで、Python アプリケーションで、あらゆる複雑さの JSON データを自信を持って処理するための基盤ができました。

さらに学習するには、以下を検討してください。

  • JSON データを返す実際の API を使用する
  • pandas などのライブラリを使用して JSON データを分析する
  • jsonschema を使用して JSON 検証を実装する
  • カスタム JSON 構造を作成および操作する