APIs speak JSON. JavaScript Object Notation is a lightweight, human-readable format for structuring data. It looks like Python dictionaries and lists. And that is no coincidence.
This lesson covers everything you need to work with APIs and JSON. You will learn to parse JSON into Python objects, convert Python objects to JSON, make API requests, handle authentication, and process JSON responses. These skills are essential for modern programming.
🕯️ Magic Note
JSON (JavaScript Object Notation) has become the universal language of web APIs. It is language-independent but looks almost identical to Python dictionaries and lists. This is why Python is so popular for API integration, the translation is almost seamless.
JSON
{
“name”: “Feloriya”,
“age”: 25,
“skills”: [“Python”, “Web Design”, “SEO”],
“is_active”: true,
“address”: {
“city”: “Tehran”,
“zip”: “12345”
},
“score”: null
}
🕯️ Magic Note
JSON supports: strings (in double quotes), numbers, booleans (true/false), null, arrays (like Python lists), and objects (like Python dictionaries). Notice that booleans are lowercase (true/false), not capitalized like Python’s True/False.
| JSON Type | Python Type |
|---|---|
| object ({}), | dict |
| array ([]), | list |
| string, | str |
| number (integer), | int |
| number (float), | float |
| true, | True |
| false, | False |
| null, | None |
Python
import json
# Python dictionary (matches JSON structure)
data = {
“name”: “Feloriya”,
“age”: 25,
“skills”: [“Python”, “Web Design”, “SEO”],
“is_active”: True,
“address”: {
“city”: “Tehran”,
“zip”: “12345”
},
“score”: None
}
Python
import json
# Parse JSON string to Python (json.loads)
json_string = ‘{“name”: “Ali”, “age”: 25, “city”: “Tehran”}’
data = json.loads(json_string)
print(data[“name”]) # Ali
print(type(data)) # <class ‘dict’>
# Parse JSON array
json_array = ‘[{“name”: “Ali”}, {“name”: “Sara”}]’
users = json.loads(json_array)
print(users[0][“name”]) # Ali
# Parse JSON from file (json.load)
with open(“data.json”, “r”, encoding=”utf-8″) as f:
data = json.load(f)
print(data[“name”])
Python
import json
data = {
“name”: “Feloriya”,
“age”: 25,
“skills”: [“Python”, “Web Design”],
“is_active”: True,
“score”: None
}
# Convert to JSON string (json.dumps)
json_string = json.dumps(data)
print(json_string)
# {“name”: “Feloriya”, “age”: 25, “skills”: [“Python”, “Web Design”], “is_active”: true, “score”: null}
# Pretty print with indentation
json_string_pretty = json.dumps(data, indent=2, sort_keys=True)
print(json_string_pretty)
# Write JSON to file (json.dump)
with open(“output.json”, “w”, encoding=”utf-8″) as f:
json.dump(data, f, indent=2, ensure_ascii=False)
🕯️ Magic Note
The indent parameter creates human-readable JSON. The sort_keys parameter sorts dictionary keys alphabetically. The ensure_ascii=False allows non-ASCII characters (like Persian) to remain unescaped.
Python
import requests
import json
# Fetch data from a public API
response = requests.get(“https://api.github.com/users/octocat”)
if response.status_code == 200:
user_data = response.json() # Parse JSON directly
print(f”Name: {user_data.get(‘name’, ‘N/A’)}”)
print(f”Followers: {user_data.get(‘followers’, 0)}”)
print(f”Repos: {user_data.get(‘public_repos’, 0)}”)
else:
print(f”Error: {response.status_code}”)
# Alternative: parse response text manually
if response.status_code == 200:
user_data = json.loads(response.text)
print(user_data[“login”])
Python
import requests
# Search GitHub repositories
params = {
“q”: “python”,
“sort”: “stars”,
“order”: “desc”,
“per_page”: 5
}
response = requests.get(“https://api.github.com/search/repositories”, params=params)
if response.status_code == 200:
data = response.json()
print(f”Total results: {data[‘total_count’]}”)
for repo in data.get(“items”, []):
print(f” – {repo[‘name’]}: {repo[‘stargazers_count’]} stars”)
Python
import requests
import os
# Method 1: API Key as query parameter
params = {“api_key”: “YOUR_API_KEY”}
response = requests.get(“https://api.example.com/data”, params=params)
# Method 2: API Key in header
headers = {“X-API-Key”: “YOUR_API_KEY”}
response = requests.get(“https://api.example.com/data”, headers=headers)
# Method 3: Bearer token (JWT, OAuth)
headers = {“Authorization”: “Bearer YOUR_TOKEN”}
response = requests.get(“https://api.example.com/user”, headers=headers)
# Method 4: Basic authentication (username/password)
response = requests.get(“https://api.example.com/private”, auth=(“username”, “password”))
# Best practice: Store keys in environment variables
API_KEY = os.environ.get(“API_KEY”)
headers = {“Authorization”: f”Bearer {API_KEY}”}
Python
import requests
# POST JSON data
new_post = {
“title”: “My First Post”,
“body”: “This is the content of my post.”,
“userId”: 1
}
response = requests.post(
“https://jsonplaceholder.typicode.com/posts”,
json=new_post, # Automatically serializes to JSON
headers={“Content-Type”: “application/json”}
)
if response.status_code == 201: # Created
created = response.json()
print(f”Post created with ID: {created[‘id’]}”)
else:
print(f”Error: {response.status_code}”)
# PUT request (update entire resource)
updated_data = {“title”: “Updated Title”, “body”: “Updated content”, “userId”: 1}
response = requests.put(“https://jsonplaceholder.typicode.com/posts/1”, json=updated_data)
# PATCH request (partial update)
response = requests.patch(“https://jsonplaceholder.typicode.com/posts/1”, json={“title”: “New Title”})
# DELETE request
response = requests.delete(“https://jsonplaceholder.typicode.com/posts/1”)
print(f”Delete status: {response.status_code}”)
🕯️ Magic Note
When you use the json parameter in requests.post(), the library automatically sets Content-Type: application/json and serializes your Python dictionary to JSON. Do not manually call json.dumps() unless you need special control.
Python
import requests
import time
def safe_api_call(url, params=None, max_retries=3, delay=1):
for attempt in range(max_retries):
try:
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
return response.json()
elif response.status_code == 429: # Too Many Requests
wait_time = int(response.headers.get(“Retry-After”, delay))
print(f”Rate limited. Waiting {wait_time} seconds…”)
time.sleep(wait_time)
elif response.status_code >= 500: # Server errors
print(f”Server error {response.status_code}. Retrying…”)
time.sleep(delay * (attempt + 1))
else:
print(f”Error {response.status_code}: {response.text}”)
return None
except requests.exceptions.Timeout:
print(f”Timeout on attempt {attempt + 1}”)
time.sleep(delay * (attempt + 1))
except requests.exceptions.RequestException as e:
print(f”Request failed: {e}”)
time.sleep(delay * (attempt + 1))
print(“Max retries exceeded”)
return None
Python
import requests
from datetime import datetime
def get_weather(city, api_key):
“””Fetch current weather for a city.”””
base_url = “https://api.openweathermap.org/data/2.5/weather”
params = {
“q”: city,
“appid”: api_key,
“units”: “metric”, # Celsius
“lang”: “en”
}
try:
response = requests.get(base_url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
return {
“city”: data[“name”],
“country”: data[“sys”][“country”],
“temperature”: data[“main”][“temp”],
“feels_like”: data[“main”][“feels_like”],
“humidity”: data[“main”][“humidity”],
“pressure”: data[“main”][“pressure”],
“description”: data[“weather”][0][“description”],
“wind_speed”: data[“wind”][“speed”],
“sunrise”: datetime.fromtimestamp(data[“sys”][“sunrise”]).strftime(“%H:%M”),
“sunset”: datetime.fromtimestamp(data[“sys”][“sunset”]).strftime(“%H:%M”)
}
except requests.exceptions.RequestException as e:
print(f”Error fetching weather: {e}”)
return None
def display_weather(weather):
if not weather:
print(“No weather data available”)
return
print(“=” * 50)
print(f”Weather in {weather[‘city’]}, {weather[‘country’]}”)
print(“=” * 50)
print(f”Temperature: {weather[‘temperature’]:.1f}°C (feels like {weather[‘feels_like’]:.1f}°C)”)
print(f”Condition: {weather[‘description’].capitalize()}”)
print(f”Humidity: {weather[‘humidity’]}%”)
print(f”Pressure: {weather[‘pressure’]} hPa”)
print(f”Wind Speed: {weather[‘wind_speed’]} m/s”)
print(f”Sunrise: {weather[‘sunrise’]}, Sunset: {weather[‘sunset’]}”)
# Usage
# API_KEY = os.environ.get(“OPENWEATHER_API_KEY”)
# weather = get_weather(“Tehran”, API_KEY)
# display_weather(weather)
Python
import requests
class TaskAPIClient:
def __init__(self, base_url, api_key=None):
self.base_url = base_url.rstrip(“/”)
self.session = requests.Session()
if api_key:
self.session.headers.update({“Authorization”: f”Bearer {api_key}”})
def get_tasks(self, completed=None):
“””Get all tasks, optionally filtered by completion status.”””
params = {}
if completed is not None:
params[“completed”] = str(completed).lower()
response = self.session.get(f”{self.base_url}/tasks”, params=params)
response.raise_for_status()
return response.json()
def create_task(self, title, description=None):
“””Create a new task.”””
task_data = {“title”: title}
if description:
task_data[“description”] = description
response = self.session.post(f”{self.base_url}/tasks”, json=task_data)
response.raise_for_status()
return response.json()
def get_task(self, task_id):
“””Get a single task by ID.”””
response = self.session.get(f”{self.base_url}/tasks/{task_id}”)
response.raise_for_status()
return response.json()
def update_task(self, task_id, **kwargs):
“””Update a task (title, description, completed).”””
response = self.session.patch(f”{self.base_url}/tasks/{task_id}”, json=kwargs)
response.raise_for_status()
return response.json()
def delete_task(self, task_id):
“””Delete a task.”””
response = self.session.delete(f”{self.base_url}/tasks/{task_id}”)
response.raise_for_status()
return response.status_code == 204
# Usage example
# client = TaskAPIClient(“https://api.example.com”)
# tasks = client.get_tasks(completed=False)
# new_task = client.create_task(“Learn Python APIs”, “Study requests and JSON”)
- Forgetting to handle HTTP errors (always check response.status_code)
- Not setting timeouts on requests (requests can hang forever)
- Hard-coding API keys in source code (use environment variables)
- Assuming response.json() always works (handle JSONDecodeError)
- Ignoring rate limits (getting your IP banned)
- Not validating JSON before parsing (malformed JSON raises exception)
- How do you parse a JSON string into a Python dictionary?
- Write code to fetch data from a public API and parse the JSON response.
- What is the difference between json.load() and json.loads()?
- How do you send JSON data in a POST request?
- What is the purpose of the indent parameter in json.dumps()?
- How should you handle API authentication keys in production code?
⚡ Whisper
APIs are the pipes of the modern web. They connect applications to data. JSON is the language they speak. With requests you ask. With json you understand. A GET request fetches data. A POST request sends data. A response comes back in JSON. You parse it with .json(). Suddenly, JSON becomes Python. Lists become lists. Dictionaries become dictionaries. Strings become strings. The translation is seamless. You navigate the data with brackets and keys. data[“user”][“name”]. data[“items”][0][“price”]. This is not magic. This is data exchange. Learn the patterns. Handle errors. Respect rate limits. Secure your keys. The API world is vast. Weather, maps, payments, social media, AI. All waiting for your request. Choose an API. Fetch the data. Parse the JSON. Build something new. The web is open. Ask your questions.