Last updated: 2026-05-22

Json in python

JSON (JavaScript Object Notation) is the standard format for exchanging data between systems — between a frontend and a backend, between microservices, between your Python script and an external API. Python's built-in json module makes working with JSON straightforward: converting Python objects to JSON strings, parsing JSON strings back into Python objects, and reading and writing JSON files.


The json Module

The json module is part of Python's standard library — no installation required:

import json

It provides four core functions:

FunctionDirectionWhat It Does
json.dumps()Python → JSON stringSerialises a Python object to a JSON-formatted string
json.loads()JSON string → PythonParses a JSON string into a Python object
json.dump()Python → JSON fileWrites a Python object as JSON to a file
json.load()JSON file → PythonReads JSON from a file into a Python object

The naming follows a consistent pattern: dumps/loads work with strings (s = string); dump/load work with files.


Serialisation — json.dumps()

Serialisation converts a Python object into a JSON string. This is what you do before sending data over a network or storing it as text:

import json

user = {
    "name": "Wariz",
    "age": 20,
    "city": "Lagos",
    "is_active": True,
    "score": None,
    "tags": ["developer", "founder"]
}

json_string = json.dumps(user)
print(json_string)
# {"name": "Wariz", "age": 20, "city": "Lagos", "is_active": true, "score": null, "tags": ["developer", "founder"]}

Pretty printing with indent

By default, json.dumps() produces a compact single-line string. Use indent for human-readable output:

print(json.dumps(user, indent=4))
{
    "name": "Wariz",
    "age": 20,
    "city": "Lagos",
    "is_active": true,
    "score": null,
    "tags": [
        "developer",
        "founder"
    ]
}

Sorting keys — sort_keys

json.dumps(user, indent=4, sort_keys=True)
# Keys appear in alphabetical order

Custom separators

For the most compact output (no spaces):

json.dumps(user, separators=(",", ":"))
# {"name":"Wariz","age":20,...}

Deserialisation — json.loads()

Deserialisation parses a JSON string back into a Python object:

json_string = '{"name": "Wariz", "age": 20, "is_active": true}'

data = json.loads(json_string)
print(data["name"])       # "Wariz"
print(data["age"])        # 20
print(type(data["age"]))  # <class 'int'>

Type mapping — JSON to Python

When deserialising, JSON types are converted to their Python equivalents:

JSON TypePython Type
stringstr
number (integer)int
number (decimal)float
true / falseTrue / False
nullNone
objectdict
arraylist

Type mapping — Python to JSON

The reverse conversion when serialising:

Python TypeJSON Type
strstring
intnumber
floatnumber
True / Falsetrue / false
Nonenull
dictobject
list, tuplearray

Working with JSON Files

Reading JSON from a file — json.load()

import json

with open("data.json", "r") as file:
    data = json.load(file)

print(data["name"])    # Access like a regular Python dict

Writing JSON to a file — json.dump()

import json

user = {"name": "Wariz", "age": 20, "city": "Lagos"}

with open("data.json", "w") as file:
    json.dump(user, file, indent=4)

The file now contains:

{
    "name": "Wariz",
    "age": 20,
    "city": "Lagos"
}

Handling JSON from APIs

The most common real-world use of JSON in Python is working with API responses. Here is a typical pattern using the requests library:

import requests

response = requests.get("https://jsonplaceholder.typicode.com/users/1")

# The response body is a JSON string
# requests provides a convenience method to parse it
user = response.json()

print(user["name"])     # "Leanne Graham"
print(user["email"])    # "Sincere@april.biz"

response.json() internally calls json.loads(response.text) — it is a shortcut, not a different function.

With aiohttp (async)

import aiohttp
import asyncio

async def fetch_user(user_id):
    async with aiohttp.ClientSession() as session:
        async with session.get(f"https://jsonplaceholder.typicode.com/users/{user_id}") as response:
            return await response.json()    # Parses JSON asynchronously

user = asyncio.run(fetch_user(1))
print(user["name"])

Nested JSON

Real-world API responses are often deeply nested. Access nested data by chaining keys:

data = {
    "user": {
        "name": "Wariz",
        "address": {
            "city": "Lagos",
            "country": "Nigeria"
        },
        "skills": ["Python", "JavaScript", "React"]
    }
}

print(data["user"]["address"]["city"])    # "Lagos"
print(data["user"]["skills"][0])          # "Python"

For deeply nested structures, use .get() to avoid KeyError on missing keys:

city = data.get("user", {}).get("address", {}).get("city", "Unknown")

Handling JSON Errors

Invalid JSON — json.JSONDecodeError

If the string is not valid JSON, json.loads() raises json.JSONDecodeError:

import json

try:
    data = json.loads("this is not json")
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")

Non-serialisable types

Not all Python objects can be converted to JSON. Attempting to serialise unsupported types raises TypeError:

import json
from datetime import datetime

data = {"created_at": datetime.now()}
json.dumps(data)    # ❌ TypeError: Object of type datetime is not JSON serializable

Handling non-serialisable types with a custom encoder

Pass a default function to handle types that json cannot serialise automatically:

import json
from datetime import datetime

def custom_encoder(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()    # Convert datetime to ISO 8601 string
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

data = {"name": "Wariz", "created_at": datetime.now()}
print(json.dumps(data, default=custom_encoder, indent=4))

Alternatively, subclass json.JSONEncoder:

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super().default(obj)

json.dumps(data, cls=CustomEncoder, indent=4)

Validating JSON Structure

Python's json module only checks that a string is syntactically valid JSON — it does not validate that the structure matches an expected schema. For schema validation, use the jsonschema library:

pip install jsonschema
from jsonschema import validate, ValidationError

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer", "minimum": 0}
    },
    "required": ["name", "age"]
}

try:
    validate(instance={"name": "Wariz", "age": 20}, schema=schema)
    print("Valid")
except ValidationError as e:
    print(f"Invalid: {e.message}")

Summary

FunctionUse
json.dumps(obj)Python object → JSON string
json.dumps(obj, indent=4)Pretty-printed JSON string
json.dumps(obj, sort_keys=True)JSON string with sorted keys
json.loads(string)JSON string → Python object
json.dump(obj, file)Write Python object as JSON to a file
json.load(file)Read JSON from a file into a Python object
response.json()Parse JSON from an HTTP response (requests)
json.JSONDecodeErrorException raised for invalid JSON
default=funcCustom handler for non-serialisable types