¿Cómo acceder a claves anidadas en un objeto JSON de Python?

PythonBeginner
Practicar Ahora

Introducción

La versatilidad de Python se extiende al trabajo con datos JSON, un formato ampliamente utilizado para almacenar e intercambiar información. Las estructuras JSON pueden ser simples o complejas, con elementos anidados, de manera similar a los diccionarios y listas de Python. En este tutorial, aprenderá a acceder y extraer datos de estructuras JSON anidadas en Python a través de ejercicios prácticos.

Al final de este laboratorio, podrá navegar con confianza por objetos JSON, acceder a claves profundamente anidadas y trabajar con arrays anidados en aplicaciones Python.

Entendiendo JSON en Python

JSON (JavaScript Object Notation) es un formato ligero de intercambio de datos que es legible por humanos y analizable por máquinas. En Python, los objetos JSON se representan como diccionarios y los arrays JSON como listas.

Comencemos creando un script de Python simple para explorar datos JSON:

  1. Abra el WebIDE y cree un nuevo archivo haciendo clic en "File > New File" desde el menú.

  2. Guarde el archivo como basic_json.py en el directorio /home/labex/project/json_practice.

  3. Agregue el siguiente código a 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. Ejecute el script abriendo una terminal en WebIDE y ejecutando:
cd /home/labex/project/json_practice
python3 basic_json.py

Debería ver la siguiente salida:

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

Entendiendo el Código

  • json.dumps() convierte un objeto Python a una cadena con formato JSON
  • json.loads() analiza una cadena JSON y la convierte en un objeto Python
  • Para acceder a elementos en un objeto JSON, use la sintaxis del diccionario: nombre_del_objeto['clave']
  • Para acceder a elementos en un array JSON, use la indexación de listas: nombre_del_array[índice]

Este ejemplo demuestra las operaciones básicas para trabajar con JSON en Python. En el siguiente paso, exploraremos cómo acceder a estructuras JSON anidadas.

Accediendo a Claves de Diccionarios Anidados

Los objetos JSON a menudo contienen estructuras anidadas. En Python, podemos acceder a datos anidados usando múltiples corchetes o encadenando claves de diccionario.

Creemos un nuevo script para explorar objetos JSON anidados:

  1. En el WebIDE, cree un nuevo archivo y guárdelo como nested_dict.py en el directorio /home/labex/project/json_practice.

  2. Agregue el siguiente código a 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. Ejecute el script en la terminal:
cd /home/labex/project/json_practice
python3 nested_dict.py

Debería ver una salida similar a:

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

Entendiendo el Acceso Anidado

Cuando se trabaja con estructuras JSON anidadas, puede acceder a elementos anidados encadenando claves con corchetes:

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

Sin embargo, este enfoque puede causar errores si alguna clave en la cadena no existe. El método más seguro es usar la función get(), que le permite proporcionar un valor predeterminado si falta una clave:

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

Este patrón de acceso seguro es particularmente valioso cuando se trabaja con respuestas de API u otros datos JSON externos donde la estructura podría no ser consistente.

Trabajando con Arrays Anidados en JSON

Los datos JSON a menudo incluyen arrays (listas en Python) que pueden contener otros objetos o arrays. Exploremos cómo acceder a elementos dentro de arrays anidados.

  1. En el WebIDE, cree un nuevo archivo y guárdelo como nested_arrays.py en el directorio /home/labex/project/json_practice.

  2. Agregue el siguiente código a 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. Ejecute el script en la terminal:
cd /home/labex/project/json_practice
python3 nested_arrays.py

Debería ver una salida similar a:

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

Entendiendo el Acceso a Arrays Anidados

Trabajar con arrays anidados en JSON implica una combinación de:

  1. Acceso indexado: Use corchetes con un índice para acceder a elementos específicos del array (array[0])
  2. Acceso a propiedades anidadas: Encadene claves de diccionario e índices de array (data['departments'][0]['employees'])
  3. Iteración: Use bucles para procesar múltiples elementos en arrays

El patrón más común para trabajar con arrays anidados es usar bucles for anidados:

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

Este enfoque le permite atravesar estructuras anidadas complejas y extraer los datos específicos que necesita.

Manejo de Claves Faltantes y Prevención de Errores

Cuando se trabaja con estructuras JSON complejas, especialmente de fuentes externas como APIs, es importante manejar los posibles errores que surgen cuando faltan claves. Exploremos técnicas para acceder de forma segura a datos JSON anidados.

  1. En el WebIDE, cree un nuevo archivo y guárdelo como error_handling.py en el directorio /home/labex/project/json_practice.

  2. Agregue el siguiente código a 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. Ejecute el script en la terminal:
cd /home/labex/project/json_practice
python3 error_handling.py

Debería ver una salida similar a:

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

Entendiendo las Técnicas de Manejo de Errores

El ejemplo demuestra dos enfoques principales para acceder de forma segura a datos JSON anidados:

  1. Bloques Try-Except

    • Envuelva el acceso potencialmente riesgoso en bloques try-except
    • Capture KeyError (clave de diccionario faltante) y TypeError (intentar acceder a una clave en un no-diccionario)
    • Proporcione valores de respaldo cuando ocurran errores
  2. Método get() Encadenado

    • Use el método get() del diccionario que toma un valor predeterminado como su segundo argumento
    • Encadene múltiples llamadas get() para estructuras anidadas
    • Proporciona un código más limpio sin manejo de excepciones

El enfoque del método get() generalmente se prefiere por su legibilidad y concisión al tratar con estructuras JSON anidadas. Le permite proporcionar valores predeterminados en cada nivel de anidamiento.

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

Usar estas técnicas de manejo de errores hará que su código sea más robusto cuando trabaje con datos JSON de varias fuentes.

Ejercicio Práctico: Construyendo un Extractor de Datos JSON

Pongamos en práctica sus conocimientos creando un programa que extraiga información específica de una estructura JSON compleja. Esto podría representar un escenario del mundo real donde recibe datos JSON de una API y necesita procesarlos.

  1. En el WebIDE, cree un nuevo archivo y guárdelo como json_extractor.py en el directorio /home/labex/project/json_practice.

  2. Agregue el siguiente código a 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. Ejecute el script en la terminal:
cd /home/labex/project/json_practice
python3 json_extractor.py
  1. Después de ejecutar el script, puede ver el archivo de resumen generado:
cat weather_summary.txt

Verá el resumen del clima formateado extraído de la estructura JSON compleja.

Entendiendo el Extractor JSON

Este ejercicio práctico demuestra varios conceptos importantes para trabajar con datos JSON anidados:

  1. Extracción segura de datos

    • Usando el método get() con valores predeterminados para manejar las claves faltantes
    • Accediendo a datos anidados con llamadas get() encadenadas
    • Manejo de errores con bloques try-except
  2. Transformación de datos

    • Convertir datos JSON a un formato legible por humanos
    • Iterar a través de arrays anidados para procesar múltiples elementos
    • Extraer solo la información relevante de una estructura compleja
  3. Programación defensiva

    • Anticipar y manejar posibles problemas
    • Proporcionar valores predeterminados cuando faltan datos
    • Capturar excepciones para evitar que el programa se bloquee

Este patrón de extracción, transformación y presentación de datos JSON es común en muchas aplicaciones del mundo real, como:

  • Procesamiento de respuestas de API
  • Generación de informes a partir de datos
  • Filtrado y visualización de información para los usuarios

Siguiendo estos patrones, puede trabajar de forma fiable con datos JSON de cualquier complejidad.

Resumen

En este laboratorio, ha aprendido a trabajar con estructuras JSON anidadas en Python:

  1. Manejo básico de JSON - Conversión entre objetos Python y cadenas JSON usando json.dumps() y json.loads().

  2. Acceso a claves de diccionarios anidados - Uso de la notación de corchetes encadenados para acceder a valores profundamente anidados en objetos JSON.

  3. Trabajo con arrays anidados - Navegación y extracción de datos de arrays dentro de estructuras JSON usando indexación e iteración.

  4. Técnicas de manejo de errores - Implementación de patrones de acceso seguro con bloques try-except y el método get() para manejar claves faltantes.

  5. Extracción práctica de datos - Construcción de una aplicación completa que extrae, transforma y presenta datos de una estructura JSON compleja.

Estas habilidades son esenciales para trabajar con datos de APIs, archivos de configuración y otros escenarios de intercambio de datos. Ahora tiene la base para manejar con confianza datos JSON de cualquier complejidad en sus aplicaciones Python.

Para un aprendizaje adicional, considere explorar:

  • Trabajar con APIs reales que devuelven datos JSON
  • Usar bibliotecas como pandas para analizar datos JSON
  • Implementar la validación JSON con jsonschema
  • Crear y manipular estructuras JSON personalizadas