Techniques avancées pour la gestion du JSON imbriqué
Maintenant que nous avons exploré les approches de base pour gérer les KeyError dans les objets JSON imbriqués, examinons quelques techniques plus avancées qui peuvent rendre votre code encore plus robuste et maintenable.
-
Créez un nouveau fichier appelé advanced_techniques.py dans le WebIDE.
-
Ajoutez le code suivant pour implémenter plusieurs techniques avancées :
## Exploring advanced techniques for handling nested JSON objects
import json
from functools import reduce
import operator
## Sample JSON data with various nested structures
json_str = """
{
"user": {
"id": 12345,
"name": "Jane Smith",
"profile": {
"bio": "Software developer with 5 years of experience",
"social_media": {
"twitter": "@janesmith",
"linkedin": "jane-smith"
}
},
"skills": ["Python", "JavaScript", "SQL"],
"employment": {
"current": {
"company": "Tech Solutions Inc.",
"position": "Senior Developer"
},
"previous": [
{
"company": "WebDev Co",
"position": "Junior Developer",
"duration": "2 years"
}
]
}
}
}
"""
## Parse the JSON string into a Python dictionary
data = json.loads(json_str)
print("Loaded JSON data structure:")
print(json.dumps(data, indent=2)) ## Pretty-print the JSON data
print("\n----- Technique 1: Using a path string with split -----")
def get_by_path(data, path_string, default=None, separator='.'):
"""
Access a nested value using a dot-separated path string.
Example:
get_by_path(data, "user.profile.social_media.twitter")
"""
keys = path_string.split(separator)
## Start with the root data
current = data
## Try to traverse the path
for key in keys:
## Handle array indices in the path (e.g., "employment.previous.0.company")
if key.isdigit() and isinstance(current, list):
index = int(key)
if 0 <= index < len(current):
current = current[index]
else:
return default
elif isinstance(current, dict) and key in current:
current = current[key]
else:
return default
return current
## Test the function
paths_to_check = [
"user.name",
"user.profile.social_media.twitter",
"user.skills.1",
"user.employment.current.position",
"user.employment.previous.0.company",
"user.contact.email", ## This path doesn't exist
]
for path in paths_to_check:
value = get_by_path(data, path, "Not available")
print(f"{path}: {value}")
print("\n----- Technique 2: Using functools.reduce -----")
def get_by_path_reduce(data, path_list, default=None):
"""
Access a nested value using reduce and operator.getitem.
This approach is more concise but less flexible with error handling.
"""
try:
return reduce(operator.getitem, path_list, data)
except (KeyError, IndexError, TypeError):
return default
## Test the reduce-based function
path_lists = [
["user", "name"],
["user", "profile", "social_media", "twitter"],
["user", "skills", 1],
["user", "employment", "current", "position"],
["user", "employment", "previous", 0, "company"],
["user", "contact", "email"], ## This path doesn't exist
]
for path in path_lists:
value = get_by_path_reduce(data, path, "Not available")
path_str = "->".join([str(p) for p in path])
print(f"{path_str}: {value}")
print("\n----- Technique 3: Class-based approach -----")
class SafeDict:
"""
A wrapper class for dictionaries that provides safe access to nested keys.
"""
def __init__(self, data):
self.data = data
def get(self, *keys, default=None):
"""
Access nested keys safely, returning default if any key is missing.
"""
current = self.data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
elif isinstance(current, list) and isinstance(key, int) and 0 <= key < len(current):
current = current[key]
else:
return default
return current
def __str__(self):
return str(self.data)
## Create a SafeDict instance
safe_data = SafeDict(data)
## Test the class-based approach
print(f"User name: {safe_data.get('user', 'name', default='Unknown')}")
print(f"Twitter handle: {safe_data.get('user', 'profile', 'social_media', 'twitter', default='None')}")
print(f"Second skill: {safe_data.get('user', 'skills', 1, default='None')}")
print(f"Current position: {safe_data.get('user', 'employment', 'current', 'position', default='None')}")
print(f"Previous company: {safe_data.get('user', 'employment', 'previous', 0, 'company', default='None')}")
print(f"Email (missing): {safe_data.get('user', 'contact', 'email', default='Not provided')}")
- Enregistrez le fichier et exécutez-le en ouvrant un terminal et en tapant :
python3 advanced_techniques.py
Vous devriez voir une sortie qui démontre différentes façons d'accéder en toute sécurité aux valeurs imbriquées dans les objets JSON. Chaque technique a ses propres avantages :
- Chaîne de chemin avec split : Facile à utiliser lorsque votre chemin est défini sous forme de chaîne (par exemple, dans les fichiers de configuration)
- Reduce avec operator.getitem : Une approche plus concise, utile en programmation fonctionnelle
- Approche basée sur les classes : Fournit un wrapper réutilisable qui rend votre code plus propre et plus facile à maintenir
Maintenant, créons une application pratique qui utilise ces techniques pour traiter une structure de données JSON plus complexe :
## Create a new file called practical_example.py
- Créez un nouveau fichier appelé
practical_example.py et ajoutez le code suivant :
import json
## Sample JSON data representing a customer order system
json_str = """
{
"orders": [
{
"order_id": "ORD-001",
"customer": {
"id": "CUST-101",
"name": "Alice Johnson",
"contact": {
"email": "alice@example.com",
"phone": "555-1234"
}
},
"items": [
{
"product_id": "PROD-A1",
"name": "Wireless Headphones",
"price": 79.99,
"quantity": 1
},
{
"product_id": "PROD-B2",
"name": "Smartphone Case",
"price": 19.99,
"quantity": 2
}
],
"shipping_address": {
"street": "123 Maple Ave",
"city": "Springfield",
"state": "IL",
"zip": "62704"
},
"payment": {
"method": "credit_card",
"status": "completed"
}
},
{
"order_id": "ORD-002",
"customer": {
"id": "CUST-102",
"name": "Bob Smith",
"contact": {
"email": "bob@example.com"
// phone missing
}
},
"items": [
{
"product_id": "PROD-C3",
"name": "Bluetooth Speaker",
"price": 49.99,
"quantity": 1
}
],
"shipping_address": {
"street": "456 Oak St",
"city": "Rivertown",
"state": "CA"
// zip missing
}
// payment information missing
}
]
}
"""
## Parse the JSON data
try:
data = json.loads(json_str)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")
exit(1)
## Import our SafeDict class from the previous example
class SafeDict:
def __init__(self, data):
self.data = data
def get(self, *keys, default=None):
current = self.data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
elif isinstance(current, list) and isinstance(key, int) and 0 <= key < len(current):
current = current[key]
else:
return default
return current
def __str__(self):
return str(self.data)
## Create a SafeDict instance
safe_data = SafeDict(data)
print("Processing order information safely...")
## Process each order
for i in range(10): ## Try to process up to 10 orders
## Use SafeDict to avoid KeyError
order = safe_data.get('orders', i)
if order is None:
print(f"No order found at index {i}")
break
## Create a SafeDict for this specific order
order_dict = SafeDict(order)
## Safely extract order information
order_id = order_dict.get('order_id', default='Unknown')
customer_name = order_dict.get('customer', 'name', default='Unknown Customer')
customer_email = order_dict.get('customer', 'contact', 'email', default='No email provided')
customer_phone = order_dict.get('customer', 'contact', 'phone', default='No phone provided')
## Process shipping information
shipping = order_dict.get('shipping_address', default={})
shipping_dict = SafeDict(shipping)
shipping_address = f"{shipping_dict.get('street', default='')}, " \
f"{shipping_dict.get('city', default='')}, " \
f"{shipping_dict.get('state', default='')} " \
f"{shipping_dict.get('zip', default='')}"
## Process payment information
payment_status = order_dict.get('payment', 'status', default='Unknown')
## Calculate order total
items = order_dict.get('items', default=[])
order_total = 0
for item in items:
item_dict = SafeDict(item)
price = item_dict.get('price', default=0)
quantity = item_dict.get('quantity', default=0)
order_total += price * quantity
## Print order summary
print(f"\nOrder ID: {order_id}")
print(f"Customer: {customer_name}")
print(f"Contact: {customer_email} | {customer_phone}")
print(f"Shipping Address: {shipping_address}")
print(f"Payment Status: {payment_status}")
print(f"Order Total: ${order_total:.2f}")
print(f"Items: {len(items)}")
## Print item details
for j, item in enumerate(items):
item_dict = SafeDict(item)
name = item_dict.get('name', default='Unknown Product')
price = item_dict.get('price', default=0)
quantity = item_dict.get('quantity', default=0)
print(f" {j+1}. {name} (${price:.2f} × {quantity}) = ${price*quantity:.2f}")
- Enregistrez le fichier et exécutez-le :
python3 practical_example.py
Vous devriez voir une sortie qui démontre comment traiter en toute sécurité une structure de données JSON complexe, en gérant avec élégance les données manquantes ou incomplètes. Ceci est particulièrement important lorsque vous traitez des données provenant de sources externes, où la structure peut ne pas toujours correspondre à vos attentes.
L'exemple pratique démontre comment :
- Naviguer en toute sécurité dans les structures JSON imbriquées
- Gérer les données manquantes avec des valeurs par défaut appropriées
- Traiter les collections d'objets dans le JSON
- Extraire et formater les informations imbriquées
Ces techniques vous aideront à créer des applications plus robustes qui peuvent gérer les données JSON du monde réel sans planter en raison d'exceptions KeyError.