Read and Write JSON Files in Python

Learn how to use Python's json module to read JSON from files and strings, write Python objects as JSON, and handle common serialization errors.

7 min read

Python's json module, part of the Python standard library, lets you read and write JSON (JavaScript Object Notation) files and strings. JSON is the most common data format for APIs, configuration files, and data exchange between systems, and it is human-readable, language-independent, and maps naturally to Python's built-in types.

pythonpython
import json
 
data = '{"name": "Maya", "score": 42, "active": true}'
parsed = json.loads(data)
print(parsed["name"])         # Maya
print(type(parsed["score"]))  # <class 'int'>

json.loads() parses a JSON string into a Python dictionary. JSON true becomes Python True, and JSON numbers become Python int or float based on whether they have a decimal point.

JSON type mapping

Python and JSON use different type names, but the mapping is straightforward.

JSON typePython type
objectdict
arraylist
stringstr
number (integer)int
number (real)float
trueTrue
falseFalse
nullNone

The json module handles these conversions automatically when reading and writing.

Reading JSON from a string

Use json.loads() (load string) to parse JSON from a string variable.

pythonpython
import json
 
response = '{"items": [{"id": 1, "label": "apple"}, {"id": 2, "label": "banana"}]}'
data = json.loads(response)
 
for item in data["items"]:
    print(item["label"])

The loop prints one line per item in the array, apple then banana, because iterating a parsed JSON array works exactly like iterating any other Python list. json.loads(response) turns the string into a Python dict. You can then access nested keys and iterate over arrays with normal Python syntax.

Reading JSON from a file

Use json.load() to read JSON directly from a file object. The file must be opened in text mode.

pythonpython
import json
 
with open("config.json") as file:
    config = json.load(file)
 
print(config.get("theme", "light"))  # dark

Assuming config.json contains {"theme": "dark", "version": 2}, json.load(file) reads and parses the file in one step. The with statement ensures the file is closed after reading, as covered in using the with statement for files.

Writing JSON to a string

Use json.dumps() (dump string) to convert a Python object to a JSON string.

pythonpython
import json
 
user = {
    "name": "Maya",
    "active": True,
    "tags": ["python", "beginner"],
    "score": None,
}
 
print(json.dumps(user))

Calling dumps on the dictionary produces a single compact line with the keys and values encoded as valid JSON syntax, ready to send over a network or write to disk:

texttext
{"name": "Maya", "active": true, "tags": ["python", "beginner"], "score": null}

json.dumps(user) converts the Python dict to a compact JSON string. Python True becomes JSON true and Python None becomes JSON null.

Writing JSON to a file

Use json.dump() to write JSON directly to a file object.

pythonpython
import json
 
settings = {"theme": "dark", "font_size": 14, "autosave": True}
 
with open("settings.json", "w") as file:
    json.dump(settings, file)

json.dump(settings, file) writes the dictionary as JSON to settings.json. The file is created if it does not exist or overwritten if it does.

Formatting JSON output for readability

Compact JSON is hard to read. Use indent to add line breaks and spacing.

pythonpython
import json
 
data = {"cities": ["Paris", "Tokyo", "Nairobi"], "count": 3}
 
print(json.dumps(data, indent=2))

Passing indent=2 tells dumps to add a newline and two spaces of indentation for every level of nesting, turning the single compact line into a readable, multi-line structure that mirrors how the data is organized:

jsonjson
{
  "cities": [
    "Paris",
    "Tokyo",
    "Nairobi"
  ],
  "count": 3
}

indent=2 adds two spaces per nesting level. Use this for configuration files, debug output, or any JSON meant for human readers.

Use sort_keys=True to alphabetize keys for consistent output:

pythonpython
print(json.dumps(data, indent=2, sort_keys=True))

Combine indent with ensure_ascii=False if your data contains non-ASCII characters like accented letters or emoji, since the default settings would otherwise escape every one of those characters into unreadable numeric codes:

pythonpython
import json
 
message = {"text": "Café résumé"}
print(json.dumps(message, ensure_ascii=False))  # {"text": "Café résumé"}

Without ensure_ascii=False, the accented characters would be escaped as \u00e9 sequences.

Handling non-serializable types

JSON only supports dicts, lists, strings, numbers, booleans, and None. If your data contains Python objects like datetime, Decimal, or custom classes, json.dumps raises a TypeError.

pythonpython
import json
from datetime import datetime
 
data = {"timestamp": datetime.now()}
json.dumps(data)

Running this raises an error immediately, because the json module has no built-in rule for turning a datetime object into JSON text on its own:

texttext
TypeError: Object of type datetime is not JSON serializable

The simplest fix is to convert the value to a string before serialization, since a plain string is one of the types the json module already knows how to encode without any extra configuration:

pythonpython
import json
from datetime import datetime
 
data = {"timestamp": datetime.now().isoformat()}
print(json.dumps(data))  # {"timestamp": "2026-07-12T14:30:05.123456"}

Converting every field by hand does not scale once several different objects need the same treatment. For more complex cases, provide a custom default function that dumps calls automatically whenever it meets a type it cannot serialize on its own:

pythonpython
import json
from datetime import datetime
 
def serialize(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError(f"Type {type(obj)} not serializable")
 
data = {"created_at": datetime.now()}
print(json.dumps(data, default=serialize))

The default function is called for every value that json.dumps cannot handle. Return a serializable representation or raise TypeError.

Handling malformed JSON

When parsing JSON from external sources like API responses, the input might be malformed. Always handle json.JSONDecodeError.

pythonpython
import json
 
raw = '{"name": "Maya", "score": 42,}'  # trailing comma
 
try:
    data = json.loads(raw)
except json.JSONDecodeError as err:
    print(f"Invalid JSON: {err}")

The trailing comma after 42 is not allowed in JSON, so parsing fails and the except block catches the resulting decode error and prints a readable message instead of crashing the program:

texttext
Invalid JSON: Expecting property name enclosed in double quotes: line 1 column 30 (char 29)

json.JSONDecodeError includes the line, column, and character position where parsing failed. This is helpful for debugging API integrations or user-supplied JSON.

Practical example: reading and filtering API data

A common pattern is to read a JSON file, filter its contents, and write the result.

pythonpython
import json
 
with open("products.json") as file:
    products = json.load(file)
 
in_stock = [p for p in products if p.get("stock", 0) > 0]
 
with open("in_stock.json", "w") as file:
    json.dump(in_stock, file, indent=2)
 
print(f"Found {len(in_stock)} products in stock.")

The script reads a list of products, filters to items with stock above zero using a list comprehension, and writes the filtered list to a new file with readable formatting.

Common mistakes

Using single quotes in JSON. JSON requires double quotes for strings and keys. Python's json.loads rejects single-quoted strings. Use json.dumps to generate valid JSON, and never hand-write JSON with single quotes.

Forgetting to open files in text mode. json.load and json.dump require text-mode file objects. Opening a file with "rb" or "wb" (binary mode) causes a TypeError. Use "r" and "w" by default.

Parsing the same JSON string twice. If json.loads succeeds, the result is already a Python object. Calling json.loads on it again will fail because it is no longer a string.

Passing a string to json.load. json.load expects a file object, not a string. Use json.loads for strings.

Rune AI

Rune AI

Key Insights

  • Use json.load(file) to read JSON from a file and json.loads(string) to parse a JSON string.
  • Use json.dump(obj, file) to write JSON to a file and json.dumps(obj) to produce a JSON string.
  • JSON maps dicts, lists, strings, numbers, booleans, and None between Python and JSON automatically.
  • Use indent for readable output and ensure_ascii=False for non-ASCII characters.
  • Provide a default function to json.dumps to handle datetime and other non-serializable types.
  • Catch json.JSONDecodeError when parsing untrusted or potentially malformed JSON input.
RunePowered by Rune AI

Frequently Asked Questions

What JSON types does Python support?

Python's json module maps JSON objects to Python dicts, JSON arrays to Python lists, JSON strings to Python str, JSON numbers to Python int or float, JSON true/false to Python True/False, and JSON null to Python None.

Why do I get a TypeError when writing a datetime to JSON?

The json module does not know how to serialize datetime objects by default. You need to convert the datetime to a string first, or provide a custom `default` function that handles datetime and other non-standard types.

What is the difference between json.load and json.loads?

`json.load(file)` reads JSON from a file object. `json.loads(string)` reads JSON from a string. The trailing 's' stands for 'string'. Same pattern: `json.dump(obj, file)` writes to a file, `json.dumps(obj)` returns a string.

Conclusion

The json module is the standard way to work with JSON in Python. Use json.load and json.dump for files, json.loads and json.dumps for strings. When you encounter types that JSON cannot handle natively, write a custom encoder or convert the values before serialization.