Serialization and Data Formats
The json module is the standard way to serialize Python objects to JSON and back. It ships with CPython and uses a C extension for performance.
Encoding and Decoding
Section titled “Encoding and Decoding”import json
data = { "host": "db-primary", "port": 5432, "replicas": ["db-replica-1", "db-replica-2"], "config": {"pool_size": 10, "timeout": 30.5}, "read_only": False, "version": None,}
## Serialize to stringjson_str = json.dumps(data, indent=2, sort_keys=True)print(json_str)
## Deserialize from stringparsed = json.loads(json_str)print(parsed["host"]) # db-primaryFile Operations
Section titled “File Operations”import json
# Write to filewith open("config.json", "w") as f: json.dump(data, f, indent=2)
# Read from filewith open("config.json") as f: config = json.load(f)Custom Encoders
Section titled “Custom Encoders”json.dumps accepts a default parameter for objects that are not JSON-serializable by default:
import jsonfrom datetime import datetime, timezonefrom decimal import Decimal
class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() if isinstance(obj, Decimal): return float(obj) if isinstance(obj, set): return list(obj) if isinstance(obj, bytes): return obj.decode("utf-8", errors="replace") return super().default(obj)
data = { "created": datetime(2025, 1, 15, 10, 30, tzinfo=timezone.utc), "price": Decimal("19.99"), "tags": {"python", "json"},}
json_str = json.dumps(data, cls=CustomEncoder, indent=2)print(json_str)Custom Decoders
Section titled “Custom Decoders”import jsonfrom datetime import datetime
def custom_decoder(dct): if "created" in dct and isinstance(dct["created"], str): dct["created"] = datetime.fromisoformat(dct["created"]) return dct
json_str = "{"created": "2025-01-15T10:30:00+00:00", "name": "test"}'data = json.loads(json_str, object_hook=custom_decoder)print(type(data["created"])) # <class 'datetime.datetime'>JSON Performance
Section titled “JSON Performance”import jsonimport timeit
data = {"key": "value", "numbers": list(range(1000))}
# dumps/load is faster than dump/load for in-memory operations# C extension is ~10x faster than pure Python fallback
t = timeit.timeit(lambda: json.dumps(data), number=100000)print(f"dumps: {t:.3f}s for 100000 iterations")pickle
Section titled “pickle”pickle serializes Python objects into a binary format. Unlike JSON, it can serialize almost any Python object, including custom classes, functions, and circular references.
Basic Usage
Section titled “Basic Usage”import pickle
class Server: def __init__(self, host, port, credentials): self.host = host self.port = port self.credentials = credentials
def __repr__(self): return f"Server({self.host!r}, {self.port})"
srv = Server("db.example.com", 5432, {"user": "admin", "pass": "secret"})
# Serializedata = pickle.dumps(srv, protocol=pickle.HIGHEST_PROTOCOL)
# Deserializesrv2 = pickle.loads(data)print(srv2) # Server('db.example.com', 5432)print(srv2.credentials) # {'user': "admin'', "pass': "secret''}Protocol Versions
Section titled “Protocol Versions”| Protocol | Python Version | Features |
|---|---|---|
| 0 | All | ASCII, human-readable, backward compatible |
| 1 | 1.4 | Binary format |
| 2 | 2.3 | New-style classes |
| 3 | 3.0 | Bytes objects, no implicit string conversion |
| 4 | 3.4 | Large objects, more types |
| 5 | 3.8 | Out-of-band data, ZSTD compression support |
import pickle
print(pickle.DEFAULT_PROTOCOL) # 5 (on Python 3.12)print(pickle.HIGHEST_PROTOCOL) # 5
# Use highest protocol for best performance and compatibilitydata = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)cloudpickle
Section titled “cloudpickle”The standard pickle cannot serialize lambda functions, dynamically defined classes, or objects Defined in __main__. cloudpickle extends pickle to handle these cases:
import cloudpickle
# Standard pickle cannot handle this:func = lambda x: x ** 2# pickle.dumps(func) # AttributeError: Can"t pickle local object
# cloudpickle handles it:data = cloudpickle.dumps(func)func2 = cloudpickle.loads(data)print(func2(5)) # 25cloudpickle is used by distributed computing frameworks like PySpark, Dask, and Ray to ship Closures across processes.
Pickle and __reduce__
Section titled “Pickle and __reduce__”The __reduce__ method controls how an object is pickled:
import pickle
class Config: def __init__(self, path): self.path = path self._data = self._load(path)
def _load(self, path): return {"loaded_from": path}
def __reduce__(self): # Return (callable, args) — pickle will call Config(*args) return (Config, (self.path,))
c = Config("/etc/app/config.yaml")data = pickle.dumps(c)c2 = pickle.loads(data)print(c2._data) # {'loaded_from': "/etc/app/config.yaml''}YAML is a human-readable data serialization format. Python uses the third-party PyYAML library.
Basic Usage
Section titled “Basic Usage”import yaml
config = { "database": { "host": "localhost", "port": 5432, "name": "production", }, "logging": { "level": "INFO", "handlers": ["stdout", "file"], },}
# Serializeyaml_str = yaml.dump(config, default_flow_style=False, sort_keys=False)print(yaml_str)
# Deserializedata = yaml.safe_load(yaml_str)print(data["database"]["host"]) # localhostsafe_load vs load
Section titled “safe_load vs load”Custom Tags with Safe Loader
Section titled “Custom Tags with Safe Loader”import yaml
# Define a custom constructor that only creates known typesdef env_variable_constructor(loader, node): value = loader.construct_scalar(node) import os return os.getenv(value, value)
# Register with a custom SafeLoaderclass CustomSafeLoader(yaml.SafeLoader): pass
CustomSafeLoader.add_constructor("!env", env_variable_constructor)
yaml_str = """host: !env HOST_NAMEport: !env PORT_NUMBER"""data = yaml.load(yaml_str, Loader=CustomSafeLoader)print(data) # {'host': "localhost'', "port': "localhost''} (or env values)TOML (Tom”s Obvious Minimal Language) is designed for configuration files. Python 3.11+ includes tomllib in the standard library.
Reading TOML (Python 3.11+)
Section titled “Reading TOML (Python 3.11+)”import tomllib # Python 3.11+
with open("pyproject.toml", "rb") as f: config = tomllib.load(f)
print(config["project"]["name"])print(config["tool"]["pytest"]["testpaths"])Reading TOML (Python 3.10 and earlier)
Section titled “Reading TOML (Python 3.10 and earlier)”# pip install tomliimport tomli
with open("pyproject.toml", "rb") as f: config = tomli.load(f)Writing TOML
Section titled “Writing TOML”Python’s built-in tomllib is read-only. For writing, use tomli_w:
# pip install tomli_wimport tomli_w
config = { "database": { "host": "localhost", "port": 5432, }, "logging": { "level": "INFO", },}
with open("config.toml", "wb") as f: tomli_w.dump(config, f)TOML Syntax
Section titled “TOML Syntax”[server]host = "0.0.0.0"port = 8080debug = false
[server.cors]allowed_origins = ["https://example.com", "https://app.example.com"]max_age = 3600
[database]host = "localhost"port = 5432name = "myapp"
[[users]]name = "admin"role = "superuser"
[[users]]name = "viewer"role = "readonly"The csv module handles reading and writing CSV files.
Reading CSV
Section titled “Reading CSV”import csv
# Basic readingwith open("data.csv", newline="") as f: reader = csv.reader(f) headers = next(reader) for row in reader: print(dict(zip(headers, row)))
# DictReader — recommended for most use caseswith open("data.csv", newline="") as f: reader = csv.DictReader(f) for row in reader: print(row["name"], row["email"])Writing CSV
Section titled “Writing CSV”import csv
headers = ["name", "email", "role"]rows = [ {"name": "Alice", "email": "alice@example.com", "role": "admin"}, {"name": "Bob", "email": "bob@example.com", "role": "user"},]
with open("output.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=headers) writer.writeheader() writer.writerows(rows)Quoting and Special Characters
Section titled “Quoting and Special Characters”import csv
# Control quoting behaviorwith open("quoted.csv", "w", newline="") as f: writer = csv.writer(f, quoting=csv.QUOTE_ALL) writer.writerow(["name", "description"]) writer.writerow(['Alice', 'Has a "title" and, commas'])
# csv.QUOTE_MINIMAL (default) — quote only when necessary# csv.QUOTE_ALL — quote everything# csv.QUOTE_NONNUMERIC — quote non-numeric values# csv.QUOTE_NONE — never quote (raises error if quoting needed)Intuition
Section titled “Intuition”Serialization is converting objects to bytes or strings for storage or transmission. json is the universal format for web APIs, pickle serialises arbitrary Python objects (but never unpickle untrusted data), and csv handles tabular data. dataclasses.asdict() converts dataclasses to dictionaries, making JSON serialisation trivial. The key trade-off is between human readability (JSON, CSV) and Python-specific capability (pickle, marshal).