PythonWorking with JSON

Working with JSON

JSON (JavaScript Object Notation) is the de facto standard for exchanging structured data between systems — APIs return it, config files use it, and log pipelines are full of it. Python's built-in json module converts between JSON text and native Python objects (dicts, lists, strings, numbers, booleans, None) without needing any third-party library. Because it's part of the standard library, all you need is import json.

Serializing: `json.dumps()`

json.dumps() ("dump string") takes a Python object and returns a JSON-formatted str. This is what you'd use to build a request body, write JSON to a socket, or log structured data as text.

Python
import json

user = {
    "name": "Ada Lovelace",
    "age": 36,
    "active": True,
    "roles": ["admin", "editor"],
    "manager": None,
}

json_text = json.dumps(user)
print(json_text)
print(type(json_text))
{"name": "Ada Lovelace", "age": 36, "active": true, "roles": ["admin", "editor"], "manager": null}
<class 'str'>
Deserializing: `json.loads()`

json.loads() ("load string") does the reverse — it parses a JSON-formatted string and returns the equivalent Python object.

Python
import json

payload = '{"id": 42, "tags": ["python", "json"], "score": 9.5}'
data = json.loads(payload)

print(data)
print(type(data))
print(data["tags"])
{'id': 42, 'tags': ['python', 'json'], 'score': 9.5}
<class 'dict'>
['python', 'json']
Reading and Writing Files: `json.load()` / `json.dump()`

When your JSON lives in a file rather than a string, use json.dump() and json.load() (no "s"). These work directly with an open file object, so you don't need to manually read the file into a string first.

Python
import json

config = {"debug": False, "max_retries": 3, "hosts": ["a.example.com", "b.example.com"]}

# Write to a file
with open("config.json", "w") as f:
    json.dump(config, f)

# Read it back
with open("config.json", "r") as f:
    loaded = json.load(f)

print(loaded)
print(loaded == config)
{'debug': False, 'max_retries': 3, 'hosts': ['a.example.com', 'b.example.com']}
True
Type Mapping

JSON has a small, fixed set of types. Python's richer type system is mapped onto it as follows:

Python

JSON

dict

object

list, tuple

array

str

string

int, float

number

True / False

true / false

None

null

Note
The mapping is lossy in one direction: both `list` and `tuple` become a JSON array, so after a round trip through `dumps`/`loads` a tuple comes back as a `list`. JSON has no tuple type, so this information simply can't survive the conversion.
Pretty-Printing with `indent` and `sort_keys`

By default dumps()/dump() produce compact, single-line output — great for wire transfer, hard to read. Pass indent= (a number of spaces) to pretty-print, and sort_keys=True to get deterministic, alphabetically-ordered keys (handy for diffs and tests).

Python
import json

record = {"name": "Ada", "id": 7, "roles": ["admin", "editor"]}

pretty = json.dumps(record, indent=2, sort_keys=True)
print(pretty)
{
  "id": 7,
  "name": "Ada",
  "roles": [
    "admin",
    "editor"
  ]
}
Serializing Objects That Aren't JSON-Native

json.dumps() only knows how to handle the types in the table above. Pass it something else — a datetime, a custom class instance, a set — and it raises a TypeError. The fix is the default= parameter: a function that's called for any object the encoder doesn't recognize, and returns something JSON-serializable in its place.

Python
import json
from datetime import datetime

event = {
    "name": "deploy",
    "timestamp": datetime(2026, 7, 6, 14, 30),
}


def default(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


print(json.dumps(event, default=default))
{"name": "deploy", "timestamp": "2026-07-06T14:30:00"}

The same default= argument works with json.dump() when writing to a file. It's only ever called for values the encoder can't already handle natively.

Handling Malformed JSON

Parsing text that isn't valid JSON — a trailing comma, single quotes instead of double quotes, an unterminated string — raises json.JSONDecodeError, a subclass of ValueError. Any code that parses JSON from an external source (a network response, user input, a file you don't fully control) should expect this and handle it.

Python
import json

bad_json = "{'name': 'Ada'}"  # single quotes are not valid JSON

try:
    data = json.loads(bad_json)
except json.JSONDecodeError as exc:
    print(f"Failed to parse JSON: {exc}")
Failed to parse JSON: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
Tip
`json.JSONDecodeError` carries `.lineno`, `.colno`, and `.pos` attributes, which is why the default error message can point at the exact character that broke parsing — useful when validating uploaded or hand-edited JSON files.
Warning
JSON keys are always strings. If you `dumps()` a dict with non-string keys (e.g. integers or tuples), Python will silently convert them to strings in the output. Round-tripping such a dict through `dumps`/`loads` will not give you back the original key types.
  • json.dumps() / json.loads() convert to and from JSON strings.

  • json.dump() / json.load() read and write JSON directly to an open file.

  • Use default= to teach the encoder how to serialize custom or non-native types.

  • Use indent= for human-readable output and sort_keys=True for deterministic key ordering.

  • Always wrap parsing of external JSON in a try/except json.JSONDecodeError block.