Web Analytics

JSON Files

Intermediate ~30 min read

JSON (JavaScript Object Notation) is the lingua franca of data exchange on the web. Every API speaks JSON, configuration files use it, and it's the go-to format for storing structured data. Python's built-in json module makes it easy to parse JSON strings, serialize Python objects, read/write JSON files, and even handle custom types like dates that JSON doesn't natively support!

Parsing JSON (loads)

The json.loads() function parses a JSON string and returns the corresponding Python object. JSON objects become Python dicts, arrays become lists, strings stay strings, numbers become int or float, booleans become True/False, and null becomes None.

Output
Click Run to execute your code
JSON to Python Type Mapping:
object {} โ†’ dict
array [] โ†’ list
string โ†’ str
number (int) โ†’ int
number (float) โ†’ float
true/false โ†’ True/False
null โ†’ None

Serializing to JSON (dumps)

The json.dumps() function converts Python objects to JSON strings. Use indent for pretty printing, sort_keys for consistent ordering, and ensure_ascii to control Unicode handling.

Output
Click Run to execute your code
Remember the 's':
loads() and dumps() = work with strings
load() and dump() = work with files
The 's' stands for 'string'!

Reading and Writing JSON Files

For files, use json.load() (without 's') to read and json.dump() to write. These work directly with file objects. The JSON Lines format (one JSON object per line) is great for log files and streaming data.

Output
Click Run to execute your code
JSON Lines (.jsonl) Format:
Each line is a valid JSON object. Great for:
- Log files (append new entries)
- Streaming data (process line by line)
- Large datasets (no memory issues)
- Data pipelines (easy to split/merge)

Custom Types and Encoders

JSON only supports basic types. For datetime, Decimal, sets, or custom classes, you need a custom encoder. Subclass JSONEncoder or use the default parameter. For decoding custom types, use object_hook.

Output
Click Run to execute your code
Not JSON Serializable! These Python types will raise TypeError:
- datetime, date, time
- Decimal, complex
- set, frozenset
- bytes, bytearray
- Custom class instances
Use a custom encoder to handle these!

Common Mistakes

1. Confusing load/loads and dump/dumps

# Wrong - using loads() with a file!
with open("data.json", "r") as f:
    data = json.loads(f)  # TypeError!

# Correct - use load() for files
with open("data.json", "r") as f:
    data = json.load(f)

# For strings, use loads()
json_string = '{"key": "value"}'
data = json.loads(json_string)

2. Single quotes in JSON

# Wrong - JSON requires double quotes!
bad_json = "{'name': 'Alice'}"  # Single quotes!
data = json.loads(bad_json)  # JSONDecodeError!

# Correct - use double quotes
good_json = '{"name": "Alice"}'
data = json.loads(good_json)

3. Trailing commas

# Wrong - JSON doesn't allow trailing commas!
bad_json = '{"a": 1, "b": 2,}'  # Trailing comma!
data = json.loads(bad_json)  # JSONDecodeError!

# Correct - no trailing comma
good_json = '{"a": 1, "b": 2}'
data = json.loads(good_json)

4. Trying to serialize non-JSON types

from datetime import datetime

# Wrong - datetime is not serializable!
data = {"created": datetime.now()}
json.dumps(data)  # TypeError!

# Correct - convert to string first
data = {"created": datetime.now().isoformat()}
json.dumps(data)  # Works!

# Or use a custom encoder
class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

5. Not handling JSON errors

# Wrong - crashes on invalid JSON!
user_input = request.get_json()  # Could be invalid!
data = json.loads(user_input)

# Correct - handle errors
try:
    data = json.loads(user_input)
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")
    data = {}

Exercise: Configuration Manager

Task: Create a configuration manager that loads, updates, and saves settings.

Requirements:

  • Load configuration from a JSON file (or use defaults if not exists)
  • Provide get and set methods for settings
  • Save changes back to the file
  • Use pretty printing for readability
Output
Click Run to execute your code
Show Solution
import json
import os

class ConfigManager:
    """Simple configuration manager using JSON."""

    def __init__(self, filename, defaults=None):
        self.filename = filename
        self.config = defaults or {}
        self.load()

    def load(self):
        """Load config from file, keep defaults if not exists."""
        try:
            with open(self.filename, "r") as f:
                loaded = json.load(f)
                self.config.update(loaded)
        except FileNotFoundError:
            print(f"No config file, using defaults")
        except json.JSONDecodeError:
            print(f"Invalid JSON, using defaults")

    def save(self):
        """Save config to file with pretty printing."""
        with open(self.filename, "w") as f:
            json.dump(self.config, f, indent=2)
        print(f"Saved to {self.filename}")

    def get(self, key, default=None):
        """Get a config value."""
        return self.config.get(key, default)

    def set(self, key, value):
        """Set a config value."""
        self.config[key] = value

    def __repr__(self):
        return json.dumps(self.config, indent=2)


# Test it
defaults = {
    "app_name": "MyApp",
    "debug": False,
    "max_connections": 10
}

config = ConfigManager("settings.json", defaults)
print("Initial config:")
print(config)
print()

# Modify settings
config.set("debug", True)
config.set("theme", "dark")
config.save()

# Reload and verify
config2 = ConfigManager("settings.json")
print("\nReloaded config:")
print(config2)
print(f"Debug: {config2.get('debug')}")
print(f"Theme: {config2.get('theme')}")

# Cleanup
os.remove("settings.json")

Summary

  • Parse string: data = json.loads(json_string)
  • Serialize to string: json_str = json.dumps(data)
  • Read file: data = json.load(file)
  • Write file: json.dump(data, file)
  • Pretty print: json.dumps(data, indent=2)
  • Sort keys: json.dumps(data, sort_keys=True)
  • Unicode: ensure_ascii=False for non-ASCII chars
  • Custom encoder: Subclass JSONEncoder or use default=func
  • Custom decoder: Use object_hook=func
  • Handle errors: Catch JSONDecodeError

What's Next?

Congratulations on completing the File Handling module! You've learned to read, write, and manage files in Python. Next, we'll tackle Error Handling - how to gracefully handle exceptions, create custom errors, and write robust code that doesn't crash unexpectedly!