Working With JSON Files in Python: Read and Write

Python’s built-in json module lets you read JSON from a file, turn Python data into JSON, and write the result back to disk. Use json.load() and json.dump() for files, and json.loads() and json.dumps() for JSON text held in a string.

What JSON is and how Python handles it

JSON, short for JavaScript Object Notation, is a text format for representing structured data. It is commonly used for configuration files, saved application data, and exchanging information between programs. Despite its name, JSON is not limited to JavaScript; Python can work with it through its standard library, so no separate package is needed for ordinary reading and writing.

JSON has objects, arrays, strings, numbers, Boolean values, and a null value. Python maps those concepts to familiar types:

  • A JSON object becomes a Python dict.
  • A JSON array becomes a Python list.
  • A JSON string becomes a Python str.
  • A JSON number becomes an int or float, depending on its form.
  • JSON true and false become Python True and False.
  • JSON null becomes Python None.

JSON syntax is not identical to Python syntax. For example, JSON uses lowercase true, false, and null, and object keys must be strings. A Python dictionary may contain types that JSON cannot represent directly, such as sets or custom objects; those need conversion before serialization.

Read a JSON file with json.load()

To read a file, open it in text mode and pass the file object to json.load(). A context manager closes the file even if reading or parsing fails.

import json

with open("settings.json", "r", encoding="utf-8") as file:
    settings = json.load(file)

print(settings["theme"])

If the file contains {"theme": "dark", "notifications": true}, Python creates a dictionary similar to {"theme": "dark", "notifications": True}. You can then access its values as you would with any dictionary. Check that a key exists, or use dict.get(), when the file may omit optional settings:

language = settings.get("language", "en")

The file must contain valid JSON representing a single value. A file containing two separate JSON objects one after another is not one valid JSON document for json.load(); store them in an array or use a format designed for a sequence of records.

Write a JSON file with json.dump()

Use json.dump() to serialize a Python value directly into an open file. The indent option makes the output easier to inspect, while ensure_ascii=False keeps many non-ASCII characters readable rather than escaping them.

import json

settings = {
    "theme": "dark",
    "notifications": True,
    "language": "en"
}

with open("settings.json", "w", encoding="utf-8") as file:
    json.dump(settings, file, indent=2, ensure_ascii=False)

Opening a file with "w" replaces its existing contents. That is appropriate when the file is meant to hold the latest saved state, but it can destroy data if the path is wrong or the write is interrupted. For important data, write to a temporary file and replace the original only after the new file has been written successfully. The exact replacement approach can depend on the operating system and application.

JSON does not define a special meaning for a trailing newline, but adding one can make a file more comfortable to inspect in a terminal or editor. One simple approach is to serialize to a string with json.dumps() and write that string followed by a newline.

Use strings with json.loads() and json.dumps()

The names are easy to mix up. The versions ending in “load” work with file-like objects; the versions ending in “loads” work with a string. Likewise, dump() writes to a file, while dumps() returns JSON text.

import json

data = {"course": "History", "active": True}
text = json.dumps(data)

restored = json.loads(text)
print(restored["course"])

This distinction is useful when data arrives as a text response or needs to be assembled before writing. It also matters when debugging: if you already have a Python string containing JSON, pass it to json.loads(), not json.load(). Passing a string to json.load() treats it as a file object and usually causes an error because a plain string does not provide the expected file-reading method.

Choose file modes and formatting carefully

JSON files are text files, so specify a text mode and an encoding when opening them. UTF-8 is a common choice for interoperable text. Use "r" to read and "w" to replace a file. The append mode, "a", is usually not a way to add another ordinary JSON value: appending a second object after the first makes the whole file invalid JSON.

Formatting is a trade-off between readability and compactness. An indentation setting produces a multi-line document that is easier for people to review. Without indentation, JSON is more compact, which can be useful when file size matters. Formatting does not change the underlying data represented, but key order and whitespace may affect text comparisons or tools that expect a particular layout.

For a file that is regularly edited by people, readable indentation and a consistent encoding are often practical. For data exchanged with another system, follow that system’s documented expectations rather than assuming that a particular formatting style is required.

Handle invalid files and other errors

Parsing can fail if a file is empty, truncated, or contains syntax that JSON does not allow. The module raises json.JSONDecodeError for invalid JSON text. File operations can also fail, for example when a path does not exist or permission is denied; those errors are separate from JSON parsing errors.

import json

try:
    with open("settings.json", "r", encoding="utf-8") as file:
        settings = json.load(file)
except FileNotFoundError:
    settings = {}
except json.JSONDecodeError as error:
    print(f"The JSON file is invalid: {error}")

Choose error handling according to what the program should do. Creating defaults for a missing optional settings file may be reasonable. Silently replacing a malformed file is riskier because it can hide corruption and discard information. During development, reporting the parsing error is often more useful than catching every exception and continuing with an empty dictionary.

A successful parse does not guarantee the data has the shape your program expects. Valid JSON could contain a list where your code expects an object, or omit a required key. Check types and required fields before relying on them:

if not isinstance(settings, dict):
    raise ValueError("Settings must be a JSON object")

if "theme" not in settings:
    raise ValueError("Settings are missing the theme field")

Common pitfalls and data conversion

One frequent mistake is trying to serialize a value JSON does not support. For example, a set is not directly representable. Convert it to a list if that preserves the meaning you need. Dates, decimals, and custom classes also need an explicit representation; the JSON module cannot infer how another program should interpret them.

The default argument to json.dump() or json.dumps() can provide a conversion function for unsupported values. Keep the conversion deliberate: converting every unfamiliar object to a string may produce output that looks readable but cannot be reconstructed reliably.

import json
from datetime import date

def convert(value):
    if isinstance(value, date):
        return value.isoformat()
    raise TypeError(f"Cannot serialize {type(value).__name__}")

text = json.dumps({"created": date.today()}, default=convert)

Here, a date is represented as a string. Reading the JSON later will not automatically turn that string back into a date; the program must parse it explicitly. Another subtle issue is that JSON object keys are strings. If a Python dictionary uses non-string keys, serialization may convert supported keys to strings, so the result may not recreate the original dictionary exactly.

For JSON that comes from an untrusted source, treat the parsed data as input, not as automatically trustworthy application data. JSON parsing does not execute code, but values can still be malformed for your application, unexpectedly large, or outside the range your program is designed to handle. Validate structure and impose suitable limits where the surrounding application requires them.

JSON files versus JSON Lines

A normal JSON file contains one complete JSON value, such as an object or an array. That makes it convenient for settings and small collections that are read or written as a whole. It is not automatically a good fit for a growing log where each record must be appended independently.

JSON Lines, sometimes called newline-delimited JSON, stores one complete JSON value per line. It is a different convention from a single JSON document. A basic reader can parse each non-empty line separately:

import json

with open("events.jsonl", "r", encoding="utf-8") as file:
    for line in file:
        if line.strip():
            event = json.loads(line)
            print(event)

This format can suit record-by-record processing, but each line must still contain valid JSON, and a partially written line needs appropriate handling. Do not append line-delimited records to a file that is meant to be one ordinary JSON object or array.

Frequently Asked Questions

Do I need to install a package to use JSON in Python?
No. The json module is part of Python’s standard library, so it is available in a normal Python installation.

What is the difference between json.load() and json.loads()?
json.load() reads JSON from a file-like object. json.loads() parses JSON text in a string. The same distinction applies to dump() and dumps(): one writes to a file, and the other returns a string.

How can I add data to an existing JSON file?
For a normal JSON document, read the existing value, update it in Python, and write the complete value back. Appending raw JSON text usually creates invalid syntax. For independently appended records, consider a line-based format such as JSON Lines.

Why does JSON reject single quotes or Python’s None?
JSON has its own syntax: strings use double quotes, and the null value is written as null. Python uses single quotes in some representations and spells the corresponding value None. Use the JSON module rather than trying to parse JSON with Python’s eval().

Does json.load() check that my data has the right fields?
No. It checks whether the text is valid JSON and converts it to Python values. Your program must separately check required keys, types, and any rules specific to the data.

A reliable workflow

For ordinary files, use a context manager, choose an explicit encoding, and pair the file-oriented functions with file objects. Make the expected data structure clear, handle missing files and malformed content according to the application’s needs, and validate parsed values before using them. When writing, remember that write mode replaces existing content and that JSON serialization cannot preserve every Python-specific type without an agreed conversion.

With those distinctions in mind, working with JSON is straightforward: parse a complete document into Python data, make the intended changes, and serialize the result back into a valid document. When the data is a sequence of append-only records rather than one document, select a format designed for that pattern instead.