Python JSON
Learn how to work with JSON data in Python using the json module for parsing and generating JSON.
JSON in Python
JSON is a syntax for storing and exchanging data.
JSON is text, written with JavaScript object notation.
Python has a built-in package called json, which can be used to work with JSON data.
Example - Import the json module:
import json Parse JSON - Convert from JSON to Python
If you have a JSON string, you can parse it by using the json.loads() method.
The result will be a Python dictionary.
Example - Convert from JSON to Python:
import json
# some JSON:
x = '{ "name":"John", "age":30, "city":"New York"}'
# parse x:
y = json.loads(x)
# the result is a Python dictionary:
print(y["age"]) Convert from Python to JSON
If you have a Python object, you can convert it into a JSON string by using the json.dumps() method.
Example - Convert from Python to JSON:
import json
# a Python object (dict):
x = {
"name": "John",
"age": 30,
"city": "New York"
}
# convert into JSON:
y = json.dumps(x)
# the result is a JSON string:
print(y) Convert Python objects into JSON strings
You can convert Python objects of the following types, into JSON strings:
- dict
- list
- tuple
- string
- int
- float
- True
- False
- None
Example - Convert Python objects into JSON strings:
import json
print(json.dumps({"name": "John", "age": 30}))
print(json.dumps(["apple", "bananas"]))
print(json.dumps(("apple", "bananas")))
print(json.dumps("hello"))
print(json.dumps(42))
print(json.dumps(31.76))
print(json.dumps(True))
print(json.dumps(False))
print(json.dumps(None)) Python to JSON Conversion Table
When you convert from Python to JSON, Python objects are converted into the JSON (JavaScript) equivalent:
Format the Result
The example above prints a JSON string, but it is not very easy to read, with no indentations and line breaks.
The json.dumps() method has parameters to make it easier to read the result:
Example - Use the indent parameter to define the numbers of indents:
import json
x = {
"name": "John",
"age": 30,
"married": True,
"divorced": False,
"children": ("Ann","Peter","James"),
"pets": None,
"cars": [
{"model": "BMW 230", "mpg": 27.5},
{"model": "Ford Edge", "mpg": 24.1}
]
}
print(json.dumps(x, indent=4)) You can also define the separators, default value is (", ", ": "), which means using a comma and a space to separate each object, and a colon and a space to separate keys from values:
Example - Use the separators parameter to change the default separator:
import json
x = {
"name": "John",
"age": 30,
"married": True,
"divorced": False,
"children": ("Ann","Peter","James"),
"pets": None,
"cars": [
{"model": "BMW 230", "mpg": 27.5},
{"model": "Ford Edge", "mpg": 24.1}
]
}
print(json.dumps(x, indent=4, separators=(". ", " = "))) Order the Result
The json.dumps() method has parameters to order the keys in the result:
Example - Use the sort_keys parameter to specify if the result should be sorted or not:
import json
x = {
"name": "John",
"age": 30,
"married": True,
"divorced": False,
"children": ("Ann","Peter","James"),
"pets": None,
"cars": [
{"model": "BMW 230", "mpg": 27.5},
{"model": "Ford Edge", "mpg": 24.1}
]
}
print(json.dumps(x, indent=4, sort_keys=True)) Working with JSON Files
You can read and write JSON data to files using the json module:
Example - Writing JSON to a file:
import json
data = {
"employees": [
{"name": "John", "age": 30, "department": "IT"},
{"name": "Jane", "age": 25, "department": "HR"},
{"name": "Bob", "age": 35, "department": "Finance"}
],
"company": "Tech Corp",
"founded": 2010
}
# Write JSON to file
with open('data.json', 'w') as file:
json.dump(data, file, indent=4)
print("Data written to data.json") Example - Reading JSON from a file:
import json
# Read JSON from file
try:
with open('data.json', 'r') as file:
data = json.load(file)
print("Company:", data['company'])
print("Founded:", data['founded'])
print("\nEmployees:")
for employee in data['employees']:
print(f"- {employee['name']}, Age: {employee['age']}, Dept: {employee['department']}")
except FileNotFoundError:
print("File not found!")
except json.JSONDecodeError:
print("Invalid JSON format!") Handling JSON Errors
When working with JSON, you should handle potential errors:
Example - Error handling with JSON:
import json
def safe_json_parse(json_string):
"""Safely parse JSON string with error handling."""
try:
return json.loads(json_string)
except json.JSONDecodeError as e:
print(f"JSON decode error: {e}")
return None
except TypeError as e:
print(f"Type error: {e}")
return None
# Valid JSON
valid_json = '{"name": "John", "age": 30}'
result = safe_json_parse(valid_json)
print("Valid JSON result:", result)
# Invalid JSON
invalid_json = '{"name": "John", "age": 30,}' # Extra comma
result = safe_json_parse(invalid_json)
print("Invalid JSON result:", result)
# Not a string
not_string = {"name": "John"}
result = safe_json_parse(not_string)
print("Not string result:", result) Custom JSON Encoding
You can create custom JSON encoders for complex objects:
Example - Custom JSON encoder:
import json
from datetime import datetime, date
class DateTimeEncoder(json.JSONEncoder):
"""Custom JSON encoder for datetime objects."""
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, date):
return obj.isoformat()
elif hasattr(obj, '__dict__'):
return obj.__dict__
return super().default(obj)
class Person:
def __init__(self, name, age, birth_date):
self.name = name
self.age = age
self.birth_date = birth_date
self.created_at = datetime.now()
# Create a person object
person = Person("John Doe", 30, date(1993, 5, 15))
# Convert to JSON using custom encoder
json_string = json.dumps(person, cls=DateTimeEncoder, indent=2)
print("Custom encoded JSON:")
print(json_string)
# Parse it back
parsed_data = json.loads(json_string)
print("\nParsed data:")
print(parsed_data) Working with Nested JSON
Handle complex nested JSON structures:
Example - Nested JSON operations:
import json
# Complex nested JSON
nested_data = {
"users": [
{
"id": 1,
"name": "John Doe",
"contact": {
"email": "john@example.com",
"phone": "+1-555-0123",
"address": {
"street": "123 Main St",
"city": "New York",
"country": "USA"
}
},
"preferences": {
"theme": "dark",
"notifications": True,
"languages": ["en", "es"]
}
},
{
"id": 2,
"name": "Jane Smith",
"contact": {
"email": "jane@example.com",
"phone": "+1-555-0456",
"address": {
"street": "456 Oak Ave",
"city": "Los Angeles",
"country": "USA"
}
},
"preferences": {
"theme": "light",
"notifications": False,
"languages": ["en"]
}
}
],
"metadata": {
"total_users": 2,
"last_updated": "2023-12-07T10:30:00Z"
}
}
def find_user_by_id(data, user_id):
"""Find a user by ID in nested JSON."""
for user in data.get("users", []):
if user.get("id") == user_id:
return user
return None
def get_user_city(data, user_id):
"""Get user's city from nested structure."""
user = find_user_by_id(data, user_id)
if user:
return user.get("contact", {}).get("address", {}).get("city")
return None
def update_user_preference(data, user_id, preference_key, value):
"""Update a user's preference."""
user = find_user_by_id(data, user_id)
if user:
user.setdefault("preferences", {})[preference_key] = value
return True
return False
# Usage examples
print("User 1 city:", get_user_city(nested_data, 1))
print("User 2 city:", get_user_city(nested_data, 2))
# Update preference
update_user_preference(nested_data, 1, "theme", "light")
print("Updated user 1 theme:", nested_data["users"][0]["preferences"]["theme"])
# Pretty print the entire structure
print("\nComplete data structure:")
print(json.dumps(nested_data, indent=2)) JSON Schema Validation
Validate JSON data against a schema (requires jsonschema package):
Example - JSON schema validation:
# Note: This requires 'pip install jsonschema'
# For demonstration purposes only
import json
# Define a simple schema
user_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number", "minimum": 0},
"email": {"type": "string", "format": "email"}
},
"required": ["name", "age"]
}
def validate_user_data(data):
"""Simple validation without external library."""
errors = []
if not isinstance(data, dict):
errors.append("Data must be an object")
return errors
# Check required fields
if "name" not in data:
errors.append("Missing required field: name")
elif not isinstance(data["name"], str):
errors.append("Name must be a string")
if "age" not in data:
errors.append("Missing required field: age")
elif not isinstance(data["age"], (int, float)) or data["age"] < 0:
errors.append("Age must be a non-negative number")
# Check optional email
if "email" in data and not isinstance(data["email"], str):
errors.append("Email must be a string")
return errors
# Test data
valid_user = {"name": "John Doe", "age": 30, "email": "john@example.com"}
invalid_user = {"name": 123, "age": -5}
print("Validating valid user:")
errors = validate_user_data(valid_user)
if errors:
print("Errors:", errors)
else:
print("Valid!")
print("\nValidating invalid user:")
errors = validate_user_data(invalid_user)
if errors:
print("Errors:", errors)
else:
print("Valid!") Practical JSON Examples
Configuration File Manager
import json
import os
class ConfigManager:
def __init__(self, config_file="config.json"):
self.config_file = config_file
self.config = self.load_config()
def load_config(self):
"""Load configuration from file."""
if os.path.exists(self.config_file):
try:
with open(self.config_file, 'r') as file:
return json.load(file)
except (json.JSONDecodeError, IOError) as e:
print(f"Error loading config: {e}")
return self.get_default_config()
else:
return self.get_default_config()
def get_default_config(self):
"""Return default configuration."""
return {
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"logging": {
"level": "INFO",
"file": "app.log"
},
"features": {
"debug_mode": False,
"cache_enabled": True
}
}
def save_config(self):
"""Save configuration to file."""
try:
with open(self.config_file, 'w') as file:
json.dump(self.config, file, indent=4)
return True
except IOError as e:
print(f"Error saving config: {e}")
return False
def get(self, key_path, default=None):
"""Get configuration value using dot notation."""
keys = key_path.split('.')
value = self.config
for key in keys:
if isinstance(value, dict) and key in value:
value = value[key]
else:
return default
return value
def set(self, key_path, value):
"""Set configuration value using dot notation."""
keys = key_path.split('.')
config = self.config
for key in keys[:-1]:
if key not in config:
config[key] = {}
config = config[key]
config[keys[-1]] = value
# Usage example
config = ConfigManager()
print("Database host:", config.get("database.host"))
print("Debug mode:", config.get("features.debug_mode"))
# Update configuration
config.set("features.debug_mode", True)
config.set("database.port", 3306)
# Save changes
if config.save_config():
print("Configuration saved successfully!")
print("Updated config:")
print(json.dumps(config.config, indent=2)) API Response Handler
import json
from urllib.request import urlopen
from urllib.error import URLError
class APIClient:
def __init__(self, base_url):
self.base_url = base_url
def fetch_data(self, endpoint):
"""Fetch JSON data from API endpoint."""
url = f"{self.base_url}/{endpoint}"
try:
with urlopen(url) as response:
if response.status == 200:
data = response.read().decode('utf-8')
return json.loads(data)
else:
print(f"HTTP Error: {response.status}")
return None
except URLError as e:
print(f"URL Error: {e}")
return None
except json.JSONDecodeError as e:
print(f"JSON Decode Error: {e}")
return None
def process_user_data(self, user_data):
"""Process user data from API response."""
if not user_data:
return None
processed = {
"id": user_data.get("id"),
"name": user_data.get("name", "Unknown"),
"email": user_data.get("email", "No email"),
"address": self.format_address(user_data.get("address", {})),
"company": user_data.get("company", {}).get("name", "No company")
}
return processed
def format_address(self, address):
"""Format address from API data."""
if not address:
return "No address"
parts = [
address.get("street", ""),
address.get("city", ""),
address.get("zipcode", "")
]
return ", ".join(filter(None, parts))
# Example usage (would work with a real API)
# api = APIClient("https://jsonplaceholder.typicode.com")
# user_data = api.fetch_data("users/1")
#
# if user_data:
# processed = api.process_user_data(user_data)
# print("Processed user data:")
# print(json.dumps(processed, indent=2))
# Simulated API response for demonstration
simulated_response = {
"id": 1,
"name": "Leanne Graham",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"city": "Gwenborough",
"zipcode": "92998-3874"
},
"company": {
"name": "Romaguera-Crona"
}
}
api = APIClient("https://example.com")
processed = api.process_user_data(simulated_response)
print("Processed user data:")
print(json.dumps(processed, indent=2))