Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›json — Serialization and Deserialization

Standard Library

json — Serialization and Deserialization

The json module provides a standardized way to convert complex Python objects into a textual representation suitable for storage or network transmission. Serialization ensures that state can be saved persistently, while deserialization allows applications to reconstruct Python objects from external input. This module is essential whenever you need to exchange structured data between processes, save configuration files, or interact with web-based APIs.

Serialization: Converting Objects to Strings

Serialization, often referred to as dumping, is the process of translating a Python object—such as a dictionary or a list—into a string format that follows the standards of data interchange. The json.dumps() function is the primary tool for this operation. When you call this function, the module traverses the Python object recursively, mapping Python types like dictionaries, lists, strings, numbers, and booleans to their corresponding counterparts. It is critical to understand that this process is not merely a string conversion; it is a structural transformation. If you attempt to serialize a Python object type that has no direct mapping, such as a custom class instance or a set, the function will raise a TypeError. This happens because the serializer must adhere to strict structural constraints to ensure that any compliant system can read the resulting data. By design, serialization produces a deterministic output that is platform-independent, ensuring that the stored data maintains its logical structure regardless of where it was created or where it will be eventually consumed.

import json

# A typical dictionary representing configuration data
config = {"host": "localhost", "port": 8080, "debug": True}

# Convert the dictionary to a string for transmission or storage
serialized_data = json.dumps(config)

# The result is a standard string, not a Python object
print(f"Serialized type: {type(serialized_data)}")
print(f"Serialized content: {serialized_data}")

Deserialization: Parsing Strings into Objects

Deserialization, or loading, is the inverse process of serialization. When you receive a string formatted as data, you must transform it back into a Python object to manipulate it programmatically. The json.loads() function parses the string and reconstructs the data structure in memory. The parser operates as a state machine, moving through the characters of the string and identifying markers that delineate keys, values, and nested structures. If the input string is malformed—for instance, if it contains an unclosed brace or a trailing comma that violates the specification—the function will raise a json.JSONDecodeError. This provides a safety mechanism; you are guaranteed that if the load operation succeeds, the resulting data is well-formed. Because the parser is building new Python objects, it is important to remember that the input data must be mapped correctly. For example, a JSON boolean is automatically converted to the Python True or False, and null values become None, ensuring that your application logic receives types that it understands and can safely use for conditional checks or further processing.

import json

# Incoming string data from an external source
raw_data = '{"host": "localhost", "port": 8080, "debug": true}'

# Reconstruct the Python dictionary
parsed_data = json.loads(raw_data)

# Now it can be treated as a native Python dictionary
print(f"Parsed object type: {type(parsed_data)}")
print(f"Port value: {parsed_data['port']}")

Working with Files Directly

While working with strings in memory is common, real-world applications frequently read and write data directly to files on disk. The json module provides high-level helper functions, json.dump() and json.load(), which accept file-like objects as arguments. This is significantly more efficient than manually reading a file content into a string and then passing it to the loads function, as it manages the stream buffers internally. When you open a file to save data, you should always use the 'w' or 'wt' mode. Conversely, when reading, you use 'r' or 'rt'. Because serialization writes to a stream, it is vital to ensure that your application handles file closing properly; using a 'with' statement is the idiomatic way to manage these resources automatically. This approach prevents resource leaks if an error occurs during the write process. The module handles the conversion and the stream writing concurrently, which is particularly beneficial when dealing with large datasets, as it avoids loading the entire file into memory before starting the processing phase, keeping the memory footprint of your application stable and predictable.

import json

# Writing a file using dump
data = {"user": "admin", "roles": ["read", "write"]}
with open("config.json", "w") as f:
    json.dump(data, f, indent=4) # Indent makes it human-readable

# Reading a file using load
with open("config.json", "r") as f:
    loaded_data = json.load(f)
    print(f"Loaded user: {loaded_data['user']}")

Handling Custom Types and Encoders

By default, the json module only supports basic types like dicts, lists, integers, and floats. If your application relies on custom Python classes, the serializer will fail because it does not inherently know how to map a class instance into a field-value structure. To solve this, you must provide a custom encoder. By subclassing the json.JSONEncoder class and overriding the default() method, you define a custom transformation logic. Inside this method, you check if the object is an instance of your custom class; if it is, you convert it into a dictionary representation that the library can process. This logic is recursive: the library calls the encoder until it hits a type it recognizes. This powerful feature allows you to maintain clean, object-oriented code while still being able to serialize complex application state. When deserializing, you manually reconstruct your class objects from the resulting dictionary. This clear separation of concerns ensures that your serialization logic is decoupled from your business logic, keeping your codebase maintainable even as your data schemas become increasingly complex and nested.

import json

class User:
    def __init__(self, name): self.name = name

class UserEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, User):
            return {"__type__": "User", "name": obj.name}
        return super().default(obj)

# Serializing a custom object
user = User("Alice")
print(json.dumps(user, cls=UserEncoder))

Formatting for Readability and Ordering

The output of serialization can be optimized for different environments. During development or when storing configuration files, you often want the generated string to be human-readable. The dump and dumps functions accept an 'indent' parameter; when set to an integer, it forces the library to insert newlines and spacing, creating a hierarchical visual structure. Furthermore, the 'sort_keys' parameter can be set to True to ensure that dictionaries are serialized with keys in alphabetical order. This is highly useful for version control systems; because the order of dictionary keys in standard Python is insertion-based, two identical dictionaries might produce different string outputs if keys were inserted in different orders. Sorting keys creates deterministic, reproducible output, which prevents unnecessary diffs in commit histories. These formatting options do not change the data's content, but they drastically improve the developer experience and maintainability. Always prioritize readability for configuration files and deterministic output for data that needs to be tracked or audited, as these small adjustments provide significant long-term benefits for team collaboration and system reliability.

import json

data = {"z": 1, "a": 2, "m": 3}

# Deterministic output for consistent file diffs
formatted = json.dumps(data, sort_keys=True, indent=2)
print(formatted)
# Output order: a, m, z

Key points

  • The json.dumps() function serializes Python objects into string representations.
  • The json.loads() function reconstructs Python objects from string inputs.
  • The module handles basic types automatically, while custom types require a custom encoder.
  • Direct file I/O using json.dump() and json.load() is more efficient than string handling.
  • Data must follow the strict structural requirements of the specification to be parsed.
  • Use the indent parameter to make serialized data readable for humans during development.
  • Sorting keys ensures deterministic output, which is essential for consistent version control.
  • Always wrap file interactions in context managers to ensure handles are closed properly.

Common mistakes

  • Mistake: Attempting to serialize a custom Python class instance directly using json.dump(). Why it's wrong: The json library only supports basic types like dict, list, str, int, float, and bool by default. Fix: Define a custom encoder using json.JSONEncoder or convert the object to a dictionary using vars() or __dict__.
  • Mistake: Trying to use single quotes in a string that you intend to parse as JSON. Why it's wrong: JSON specification strictly requires double quotes for keys and string values. Fix: Use double quotes for all string data within the JSON format.
  • Mistake: Assuming that json.load() and json.loads() work interchangeably on strings. Why it's wrong: json.load() expects a file-like object to read from, while json.loads() expects a string directly. Fix: Use json.loads() for string variables and json.load() for file pointers.
  • Mistake: Failing to handle potential JSONDecodeError when reading external files. Why it's wrong: External data may be malformed or truncated, which will crash the script if not caught. Fix: Wrap the parsing logic in a try-except block specifically for json.JSONDecodeError.
  • Mistake: Writing binary data directly into a JSON file. Why it's wrong: JSON is a text-based format and does not support raw bytes. Fix: Base64 encode binary data into a string format before including it in a JSON structure.

Interview questions

What is the fundamental difference between serialization and deserialization in the context of Python's json module?

In Python, serialization is the process of converting a Python object, such as a dictionary, list, or string, into a formatted string representation that adheres to the standard format. We use the json.dumps() function for this. Conversely, deserialization is the reverse process, where you take a string formatted in that notation and convert it back into native Python objects using the json.loads() function. This is critical for data persistence and network transmission because raw memory objects cannot be directly stored in text files or sent over HTTP protocols without this translation layer.

How do you save a Python dictionary to a file and read it back using the json library?

To save a dictionary to a file, you use the json.dump() function, which takes the data object and a file object as arguments. For example: `with open('data.json', 'w') as f: json.dump(my_dict, f)`. To read it back, you use json.load(f). These functions are preferred over dumps and loads because they handle the file stream operations efficiently, ensuring that the data is correctly written to or parsed from the disk without needing to manually manage the file string content yourself.

What happens if you try to serialize a custom Python class instance directly using the json module, and how can you fix it?

If you try to serialize a custom object instance, the json module will raise a TypeError because it does not know how to map custom memory structures to standard types. You fix this by either providing a custom encoder class inheriting from json.JSONEncoder and overriding the default method, or more simply, by passing a 'default' function argument to json.dumps(). This function should convert your object into a dictionary representation, typically by returning obj.__dict__, allowing the serializer to process the underlying data structure successfully.

Compare the use of json.dumps() versus json.dump() in a production Python environment.

The primary difference lies in the output target. json.dumps() returns a string object, which is useful when you need to manipulate the serialized data in memory, send it over a network socket, or log it to a monitoring service. In contrast, json.dump() writes directly to a file-like object. Using json.dump() is generally more memory-efficient when dealing with large datasets because it streams the data into the file buffer, whereas json.dumps() requires the entire serialized string to be held in memory simultaneously, which can lead to memory exhaustion issues if the object is excessively large.

How can you customize the output of serialized JSON to make it more readable or compact using keyword arguments?

You can control the formatting using parameters like 'indent', 'sort_keys', and 'separators'. By setting 'indent=4', you transform a compact, machine-readable string into a human-readable format with nested structure. Using 'sort_keys=True' ensures the dictionary keys are written in alphabetical order, which is essential for creating deterministic outputs for version control or caching comparisons. To save space, you can set 'separators=(',', ':')', which removes the default whitespace after commas and colons, creating a minified string that significantly reduces the payload size for web transmissions.

When deserializing JSON data from an external source, how should you handle potential errors to make your application robust?

When dealing with external data, you must wrap your json.loads() or json.load() calls in a try-except block specifically catching json.JSONDecodeError. This error occurs if the input is malformed, truncated, or contains syntax violations like trailing commas or single quotes. Furthermore, since deserialization only converts data into dictionaries and lists, you should always perform additional validation after parsing to ensure the expected keys exist and contain data types your application expects, preventing subsequent AttributeErrors or KeyErrors during further processing of the parsed data.

All Python interview questions →

Check yourself

1. Which of the following describes the behavior of json.dumps() when passed a Python dictionary containing a set?

  • A.It successfully serializes the set into a JSON array.
  • B.It raises a TypeError because sets are not natively JSON serializable.
  • C.It converts the set into a string representation like '{1, 2, 3}'.
  • D.It silently ignores the set and removes the key from the output.
Show answer

B. It raises a TypeError because sets are not natively JSON serializable.
The json module does not support sets. Option 0 and 2 are incorrect because there is no built-in conversion for sets, and option 3 is incorrect because the library raises an error rather than ignoring data.

2. When working with a file object, what is the correct approach to write a Python list to a JSON file named 'data.json'?

  • A.json.write(my_list, 'data.json')
  • B.json.dumps(my_list, file='data.json')
  • C.json.dump(my_list, open('data.json', 'w'))
  • D.json.dump(my_list, 'data.json')
Show answer

C. json.dump(my_list, open('data.json', 'w'))
json.dump() requires a file-like object as the second argument. Option 0 does not exist, option 1 uses the wrong function (dumps is for strings), and option 3 fails because 'data.json' is a string, not an open file stream.

3. What is the primary difference between json.load() and json.loads()?

  • A.json.load() is for writing data, while json.loads() is for reading.
  • B.json.load() reads from a file object, while json.loads() parses a JSON-formatted string.
  • C.json.load() supports complex Python objects, while json.loads() only supports primitives.
  • D.There is no difference; they are aliases for the same function.
Show answer

B. json.load() reads from a file object, while json.loads() parses a JSON-formatted string.
json.load() is designed to read from a file handle (stream), whereas json.loads() (load-string) parses a string. The other options misrepresent the functionality of the two methods.

4. If you have a Python dictionary with a datetime object, what happens if you try to serialize it using json.dumps()?

  • A.It automatically formats the date as an ISO 8601 string.
  • B.It raises a TypeError because datetime objects are not JSON serializable.
  • C.It serializes it as a nested dictionary of its components.
  • D.It treats the datetime object as None.
Show answer

B. It raises a TypeError because datetime objects are not JSON serializable.
Datetime objects are not part of the basic types supported by the JSON standard, so Python will raise a TypeError. It does not auto-format, auto-convert, or cast to None.

5. You have a JSON string with a key using single quotes. What happens when you call json.loads() on it?

  • A.It succeeds because Python is flexible with quotes.
  • B.It fails with a JSONDecodeError because JSON requires double quotes.
  • C.It automatically corrects the single quotes to double quotes.
  • D.It treats the entire string as a single value rather than an object.
Show answer

B. It fails with a JSONDecodeError because JSON requires double quotes.
JSON standards strictly mandate double quotes for keys and strings. The json module enforces this strictly, so using single quotes will result in a JSONDecodeError. The other options incorrectly imply that the parser is permissive or auto-correcting.

Take the full Python quiz →

← Previousitertools — chain, product, combinationsNext →re — Regular Expressions

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app