Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

To save a Python list or dictionary to a JSON file, open the file in text mode with encoding="utf-8", then pass the file object to json.dump(). To load it later, open the file the same way and call json.load(). A JSON file holds one document, so a group of records belongs in one list that you write once. Most failures people hit with JSON files come from three things: encoding, writing several documents to one file, and input that is not valid JSON. The sections below cover each one, along with the values Python cannot convert to JSON on its own.

Write a Python value to a JSON file

  1. Import the standard-library json module. No installation is needed.
  2. Open the target file in write mode ("w") with encoding="utf-8".
  3. Call json.dump(value, f), passing your Python object and the file object. Add indent=2 if you want the file to be easy to read.
import json

record = {"name": "Ada", "active": True, "skills": ["math", "logic"]}

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

json.dump() writes text, so the file must be opened in text mode. Passing a file opened with "wb" raises a TypeError. The Python 3.13 tutorial’s “Input and Output” section sets the encoding expectation directly: “JSON files must be encoded in UTF-8.” It also recommends passing encoding="utf-8" whenever you open a text file for JSON.

Read the file back

import json

with open("record.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["name"])   # Ada

Reading a file with json.load() returns ordinary Python objects. The conversion follows a fixed mapping:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
JSON value Python value after json.load()
object {} dict
array [] list
string str
number (integer form) int
number (with fraction or exponent) float
true / false True / False
null None

Choose between dump, dumps, load and loads

The module has a pair of functions for files and a pair for strings. Use the file functions when the data goes to or comes from disk, and the string functions when you already have the JSON text in memory, such as a value from a network response.

Function Accepts Result
json.dump(value, fp) Python value and a writable text file object Writes JSON text to fp; returns nothing
json.dumps(value) Python value Returns a str containing JSON
json.load(fp) Readable text file object Parses one JSON document and returns a Python value
json.loads(s) str, bytes or bytearray Parses one JSON document from the string and returns a Python value

The “s” at the end of dumps and loads means “string,” not “several.” Neither pair handles more than one document per call.

Format the output and handle non-ASCII text

  • Readable output: indent=2 puts each element on its own line. Leave it out for the most compact file.
  • Compact output: separators=(",", ":") removes the spaces after commas and colons. This is useful when file size matters more than readability.
  • Stable diffs: sort_keys=True writes object keys in alphabetical order, so two saves of the same data produce identical files.
  • Non-ASCII text: ensure_ascii defaults to True, which escapes characters outside ASCII. For example, {"city": "São Paulo"} is written as {"city": "São Paulo"}. Setting ensure_ascii=False writes the character itself, which works well with a UTF-8 file.

Know what changes during a round trip

JSON object keys must be strings. When you dump a dictionary whose keys are numbers, Python converts those keys to strings, so the loaded dictionary is different from the original. For example, {1: "a"} is written as {"1": "a"}, and json.load() returns {"1": "a"}. Tuples are written as JSON arrays and come back as lists. If your code depends on the exact key or container types, convert them after loading.

Why repeated dump calls produce an invalid file

A common mistake is calling json.dump() several times on the same file object, expecting each call to add a separate record. The Python json reference states the rule plainly: “Unlike pickle and marshal, JSON is not a framed protocol, so trying to serialize multiple objects with repeated calls to dump() using the same fp will result in an invalid JSON file.”

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import json

with open("log.json", "w", encoding="utf-8") as f:
    json.dump({"id": 1}, f)
    json.dump({"id": 2}, f)

The file now contains {"id": 1}{"id": 2}. That is two JSON values with nothing separating them, so a later json.load() call fails with a JSONDecodeError that reports extra data after the first document. Calling json.dump() does not add separators, so the output is not a stream of valid documents. Choose one of the two patterns below.

Store the records in one list

If the records belong together, put them in a list and dump the list once. This is the simplest option and the most widely supported.

import json

events = [{"id": 1}, {"id": 2}]

with open("log.json", "w", encoding="utf-8") as f:
    json.dump(events, f, indent=2)

with open("log.json", "r", encoding="utf-8") as f:
    events = json.load(f)

The whole file must then be read into memory before you can work with any single record, so very large collections are better handled with the next approach.

Use JSON Lines for independent records

JSON Lines stores one JSON value per line. You serialize each record with json.dumps() and add a newline. Each line can then be parsed on its own, which suits logs and streams of records. The newline is your separator, so the file format must be documented wherever the file is used.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import json

records = [{"id": 1}, {"id": 2}]

with open("log.jsonl", "w", encoding="utf-8") as f:
    for rec in records:
        f.write(json.dumps(rec) + "n")

with open("log.jsonl", "r", encoding="utf-8") as f:
    loaded = [json.loads(line) for line in f if line.strip()]

Do not use json.load() on a JSON Lines file. It expects a single document and will fail on the second line.

Validate and pretty-print from the command line

The json module can be run from the command line to check a file or reformat it. The current Python 3.14 reference documents python -m json for this purpose and keeps python -m json.tool working for backwards compatibility.

python -m json record.json
python -m json --json-lines log.jsonl
  • With a valid file, the formatted JSON is printed to standard output.
  • With an invalid file, the command reports a parsing error instead of printing the output.
  • --json-lines parses each line as a separate JSON object, so it is the right check for files created with the JSON Lines pattern above.
  • The tool can also read from standard input and write to a file, and it accepts options for sorting keys and setting indentation.

Handle invalid and unexpected input

Invalid JSON raises json.JSONDecodeError, which is a subclass of ValueError. The exception provides the position of the problem, so you can report it to the user or log it:

import json

try:
    with open("record.json", "r", encoding="utf-8") as f:
        data = json.load(f)
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e.msg} at line {e.lineno}, column {e.colno}")

Do not treat every exception as a JSON problem. A missing file raises FileNotFoundError, and a file that is not valid UTF-8 raises UnicodeDecodeError. Those errors are not JSONDecodeError, so catch them separately.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When the data comes from an untrusted source, limit its size before parsing. The Python reference warns that malicious input can consume a large amount of CPU and memory during parsing. The following check reads at most a fixed number of bytes:

import json

MAX_BYTES = 1_000_000

with open("upload.json", "rb") as f:
    raw = f.read(MAX_BYTES + 1)

if len(raw) > MAX_BYTES:
    raise ValueError("JSON input exceeds the allowed size")

data = json.loads(raw)

Reading the file in binary mode and passing the bytes to json.loads() lets the module detect the encoding from the bytes. The size limit is a resource-exhaustion control; it does not validate the structure of the data, so keep your normal checks on the loaded values.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Serialize values that JSON does not support

JSON supports only objects, arrays, strings, numbers, booleans and null. Passing a datetime, a set or a custom class to json.dump() raises a TypeError with the message Object of type X is not JSON serializable. The module will not convert these values for you. Supply a conversion function through the default argument, and have it raise an error for anything it does not handle:

import json
from datetime import date

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

payload = {"created": date(2026, 10, 9)}

with open("event.json", "w", encoding="utf-8") as f:
    json.dump(payload, f, default=encode_extra, indent=2)

The date is stored as the string "2026-10-09". After json.load() it comes back as a string, so your code must convert it to a date again. Keep the encoder and the decoding step together in one place so the two stay consistent.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

JSON or pickle

The Python tutorial contrasts JSON with pickle. Both can save Python data to a file, but they suit different jobs.

Criterion JSON pickle
Interoperability Designed for data interchange between applications and languages Specific to Python
Data types Objects, arrays, strings, numbers, booleans and null Most Python objects, including custom classes
Custom classes Need a conversion function (default) and a matching decode step Serialized directly
Untrusted input Parsing can use a lot of CPU and memory, so limit input size; the format does not execute code during loading Deserializing untrusted data is unsafe because crafted input can execute code
Human-readable Yes, as plain text No, binary format

Use JSON when another program, language or service needs to read the file. Use pickle only for Python-to-Python data that you created yourself and trust, and never load a pickle file from a source you do not control.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.