Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Work with JSON data

Learn to work with JSON data using the json module in Python. This lesson covers core concepts, step-by-step operations, and practical exercise.

Focus: work with json data using the json module

Sponsored

You've built a Python script that scrapes data from an API or collects user settings, but now you need to save it so you can load it back later. Simply writing a Python dictionary to a file won't work for other programs or even future versions of your own code. This is where JSON comes in — it's the universal, language-independent format for data exchange, and Python's json module makes parsing it dead simple. By the end of this lesson, you'll confidently convert between Python objects and JSON to read, write, and manipulate structured data in any project.

The problem this lesson solves

Imagine you're building a configuration manager. Python variables like {"theme": "dark", "language": "en"} vanish when the program ends. You could use pickle to serialize Python objects, but that format is Python-only, insecure, and unreadable. JSON (JavaScript Object Notation) solves this by providing a text-based, human-readable, and cross-platform serialization format. Every modern language — Python, JavaScript, Go, Rust — can parse JSON. The json module bridges your Python objects and this universal format without you needing to write a single parser.

Core concept / mental model

Think of JSON as a universal translator between Python and the outside world. When you call json.dumps(), Python converts a dictionary or list into a JSON string that looks like a JavaScript object. When you call json.loads(), it does the reverse: it reads a JSON string and rebuilds a Python dictionary or list. The two core methods are:

  • json.dumps(obj) — Serialize Python object → JSON string (dump to string).
  • json.loads(str) — Deserialize JSON string → Python object (load from string).

For files, use json.dump(obj, file) and json.load(file) (notice no 's') — these directly read from / write to file handles.

Key type mappings

Python JSON Example
dict Object {"key": "value"}
list Array [1, 2, 3]
str String "hello"
int / float Number 42, 3.14
bool true / false Truetrue
None null Nonenull

Pro Tip: Python tuples become JSON arrays; you lose the tuple type during conversion. If you need to preserve types, use a custom encoder or pickle.

How it works step by step

Let's walk through the complete workflow of saving and loading a settings dictionary.

Step 1: Import the json module

import json

Step 2: Prepare your Python data

settings = {
    "app_name": "MyNotepad",
    "theme": "dark",
    "language": "en",
    "font_size": 14,
    "plugins": ["spellcheck", "markdown"],
    "auto_save": True,
    "max_recent_files": None
}

Step 3: Serialize to JSON string (for display or transmission)

json_string = json.dumps(settings, indent=2)
print(json_string)

Output:

{
  "app_name": "MyNotepad",
  "theme": "dark",
  "language": "en",
  "font_size": 14,
  "plugins": ["spellcheck", "markdown"],
  "auto_save": true,
  "max_recent_files": null
}

Notice: True became true and None became null — JSON's valid tokens.

Step 4: Write to a file

with open("settings.json", "w", encoding="utf-8") as f:
    json.dump(settings, f, indent=4)

Why indent? Without it, json.dump produces one long line. indent=2 or 4 makes the file human-readable.

Step 5: Read back from the file

with open("settings.json", "r", encoding="utf-8") as f:
    loaded_settings = json.load(f)

print(loaded_settings["theme"])  # 'dark'

This is the core loop: dictjson.dump → file → json.loaddict.

Hands-on walkthrough

Let's combine all steps into a real mini-project: a contact book that saves to JSON.

Example 1: Basic CRUD with JSON

import json

# Initial contacts data
contacts = [
    {"name": "Alice", "phone": "555-1234", "email": "alice@example.com"},
    {"name": "Bob", "phone": "555-5678", "email": "bob@example.com"}
]

# Save to file
with open("contacts.json", "w") as f:
    json.dump(contacts, f, indent=2)
    print("Contacts saved!")

# Add a new contact
new_contact = {"name": "Charlie", "phone": "555-9012", "email": "charlie@example.com"}
contacts.append(new_contact)

# Overwrite the file with updated list
with open("contacts.json", "w") as f:
    json.dump(contacts, f, indent=2)
    print("Updated contacts saved!")

# Load and display all names
with open("contacts.json", "r") as f:
    loaded = json.load(f)
    for contact in loaded:
        print(f"{contact['name']} — {contact['phone']}")

Expected output:

Contacts saved!
Updated contacts saved!
Alice — 555-1234
Bob — 555-5678
Charlie — 555-9012

Example 2: Pretty printing and sorting keys

By default json.dumps outputs keys in insertion order (Python 3.7+). For deterministic output, use sort_keys=True.

data = {"c": 3, "a": 1, "b": 2}
print(json.dumps(data, indent=2, sort_keys=True))

Output:

{
  "a": 1,
  "b": 2,
  "c": 3
}

Example 3: Handling custom objects (JSONEncoder)

Python's datetime objects are not JSON-serializable by default. You must convert them manually.

from datetime import datetime

def datetime_serializer(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError(f"Type {type(obj)} not serializable")

event = {
    "title": "Python workshop",
    "date": datetime(2024, 6, 15, 14, 30)
}

json_str = json.dumps(event, default=datetime_serializer, indent=2)
print(json_str)

Output:

{
  "title": "Python workshop",
  "date": "2024-06-15T14:30:00"
}

Compare options / when to choose what

Approach When to use Pros Cons
json.dump/load File I/O (configs, data storage) Simple, direct to disk Requires file handle
json.dumps/loads Network, in-memory, printing Works on strings, flexible Must manage encoding yourself
pickle Internal Python tools Supports all Python objects Insecure, not human-readable
yaml (PyYAML) Complex configs (e.g., pip, Ansible) Supports comments, multiline Slower, requires external lib

When to pick JSON over YAML? JSON is faster, built-in, and universally supported — perfect for APIs and simple configs. Use YAML when you need comments or multi-line strings.

Troubleshooting & edge cases

Common mistakes

  • Forgetting ensure_ascii=False – When dumping non-ASCII characters (like emoji or Chinese), the default ensure_ascii=True escapes them to \uXXXX. Pass ensure_ascii=False to preserve readability. ```python # Bad: "I ❤ Python" becomes "I \u2764 Python" print(json.dumps({"text": "I ❤ Python"})) # "I \u2764 Python"

# Good: print(json.dumps({"text": "I ❤ Python"}, ensure_ascii=False)) # "I ❤ Python" `` - **Writing multiple JSON objects without an array** – Eachjson.dumpcall writes one object. Writing five calls creates a file that's invalid JSON (concatenated objects). Always collect objects into a list first. - **Usingjson.load()on a malformed file** – Always handlejson.JSONDecodeError`. Wrap in try/except.

Edge case: Empty data

empty_dict = json.loads("{}")  # Returns empty dict {}
empty_list = json.loads("[]")  # Returns empty list []
print(json.dumps(None))        # "null"

Edge case: Duplicate keys (ill-formed JSON)

Python's JSON parser by default keeps the last value for duplicate keys:

data = '{"name": "Alice", "name": "Bob"}'
print(json.loads(data))  # {'name': 'Bob'}

Fix: Use json.loads(data, object_pairs_hook=collections.OrderedDict) to preserve order and detect duplicates.

Error: TypeError after dumps with custom objects

# This will raise: TypeError: Object of type set is not JSON serializable
set_data = {"my_set": {1, 2, 3}}
json.dumps(set_data)

Solution: Convert sets to lists before serialization: {"my_list": list({1,2,3})}.

What you learned & what's next

You've mastered the core duo — json.dumps/loads and json.dump/load — enabling you to: - Serialize Python dictionaries, lists, strings, numbers, booleans, and None to JSON - Write JSON to files with indent for readability - Read JSON back and process it as Python objects - Handle custom types like datetime with a custom encoder - Avoid common pitfalls (encoding, file modes, duplicate keys)

You now have a powerful skill for configuration files, API responses, and data persistence. In the next lesson, “Work with environment variables using the os module”, you'll learn how to store sensitive data like API keys outside your code using os.environ. Ready to keep your secrets safe?

Practice recap

Create a mini program that stores a dictionary of user preferences (theme, language, volume). Use json.dump() to save it to a preferences.json file. Then write a second function that reads the file with json.load() and prints each setting. Add error handling for when the file doesn't exist yet. Challenge: Add a custom serializer for a datetime object recording the last save time.

Common mistakes

  • Using json.loads() on a file object instead of a string, causing AttributeError — always match dump (file) with load, and dumps (string) with loads.
  • Forgetting to open files in text mode with encoding='utf-8' (Python 3 defaults to UTF-8 but explicit is safer) and writing multiple JSON objects without wrapping them in a list.
  • Assuming JSON preserves Python-specific types like datetime, Decimal, or set — these must be converted to strings, floats, or lists manually before serialization.
  • Calling json.dump() multiple times on the same file without overwriting, producing concatenated JSON (invalid) — always write your entire structure in one call.

Variations

  1. Use json.dumps(fileobj, default=encoder) for custom object serialization (e.g., datetime to ISO string).
  2. Substitute json.loads() with json.load(file) when working directly with file handles — fewer lines and fewer errors.
  3. For extreme performance with large JSON arrays, consider ijson (iterative JSON parser) instead of json.loads() which loads the entire file into memory.

Real-world use cases

  • Saving user application settings as a JSON config file — read on startup, write on change.
  • Parsing responses from REST APIs like GitHub, Twitter, or weather services that return JSON.
  • Exporting SQL query results to a JSON file for import into other tools like Elasticsearch or web dashboards.

Key takeaways

  • JSON is a universal, human-readable data format — Python's json module converts between dicts/lists and JSON strings/files.
  • Use json.dumps() → string for memory/network; json.dump() → file for persistence — always pass indent for readability.
  • Use json.loads() from string; json.load() from file — handle JSONDecodeError with try/except for malformed data.
  • Python types like datetime, Decimal, and set require custom serialization; use default parameter or JSONEncoder subclass.
  • Always open JSON files with open(filename, 'r', encoding='utf-8') to support international characters and avoid encoding errors.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.