JSON в Python: эффективные приемы сериализации и десериализации

Раздел: Python -> JSON

Основные приемы работы с JSON в Python 3

Как прочитать JSON из строки и преобразовать в объект Python?

Встроенный модуль json предоставляет функцию json.loads() для десериализации JSON-строки. Это наиболее простой и распространенный способ получения словаря, списка или другого типа из текстового формата.


import json
json_string = '{"name": "Иван", "age": 30, "city": "Москва"}'
data = json.loads(json_string)
print(data)
print(type(data))

Python 3 json (работа с json в python 3)

{'name': 'Иван', 'age': 30, 'city': 'Москва'}
<class 'dict'>

Json open python (открытие json файла в python)

Функция автоматически преобразует JSON-типы: объект -> dict, массив -> list, строки -> str, числа -> int/float, булевы -> True/False, null -> None.

Возможные проблемы: если строка содержит синтаксическую ошибку (например, пропущена запятая или кавычка), возникает json.JSONDecodeError. Рекомендуется оборачивать вызов в try/except. Также при работе с большими числами может теряться точность из-за преобразования в float – для контроля используйте parse_float или parse_int.


try:
    data = json.loads('{"invalid": true}')
except json.JSONDecodeError as e:
    print(f"Ошибка разбора: {e}")

преобразовать json в словарь python (преобразование json в словарь python)

Как загрузить JSON из файла?

Для чтения JSON из файла используется json.load(), которая принимает файловый объект, открытый в режиме чтения текста.


with open('data.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

сохранить json python (сохранение json в файл python)

Параметр encoding обязателен для корректной обработки символов в кодировке UTF-8. Если файл сохранен в другой кодировке, укажите ее явно.

Ошибки: при отсутствии файла возникнет FileNotFoundError. При неправильной кодировке – UnicodeDecodeError. Если файл пуст или содержит не JSON, получите JSONDecodeError.

Как записать объект Python в JSON-файл с отступами?

Функция json.dump() сериализует объект и записывает результат в файл. Параметр indent добавляет отступы для читаемости.


data = {'name': 'Мария', 'age': 25, 'languages': ['Python', 'C++']}
with open('output.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, indent=4, ensure_ascii=False)

Python json lib (библиотека json в python)

ensure_ascii=False сохраняет не-ASCII символы (например, кириллицу) в читаемом виде, а не в виде escape-последовательностей.

Типичные ошибки: попытка записать объект, не поддерживающий сериализацию (например, экземпляр пользовательского класса без специального энкодера) вызывает TypeError. Для таких случаев нужно описать кастомный энкодер или преобразовать объект в сериализуемый тип вручную.

Как красиво отформатировать JSON-строку (pretty-print) без записи в файл?

Функция json.dumps() с параметром indent возвращает отформатированную строку.


data = {'key': 'value', 'nested': {'a': 1}}
formatted = json.dumps(data, indent=2, sort_keys=True)
print(formatted)

вложенный json python (вложенный json в python)

{
  "key": "value",
  "nested": {
    "a": 1
  }
}

Json lines python (формат json lines в python)

sort_keys=True упорядочивает ключи по алфавиту, что полезно для сравнения JSON-строк.

Внимание: при большом объеме данных форматирование с отступами увеличивает размер строки в 2-3 раза. Для экономии места используйте separators=(',', ':').

Как сериализовать объект пользовательского класса?

По умолчанию json.dumps не знает, как преобразовать произвольный объект. Можно определить метод __json__() или использовать параметр default в функциях сериализации.


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# Вариант 1: функция default
def person_to_dict(obj):
    if isinstance(obj, Person):
        return {'__person__': True, 'name': obj.name, 'age': obj.age}
    raise TypeError(f"Object of type {type(obj)} is not JSON serializable")

p = Person('Анна', 28)
json_str = json.dumps(p, default=person_to_dict, ensure_ascii=False)
print(json_str)

Python return json (возврат json из функции python)

{"__person__": true, "name": "Анна", "age": 28}

При десериализации нужно восстановить объект через object_hook.

Проблема: если в default передать функцию, которая не выбрасывает TypeError для неподходящих типов, вызов может привести к бесконечной рекурсии или неожиданным последствиям. Всегда возвращайте сериализуемое значение или вызывайте исключение.

Как обработать дублирующиеся ключи в JSON?

По стандарту RFC 8259 ключи в JSON должны быть уникальными, но на практике могут встречаться дубли. Модуль json по умолчанию оставляет последнее значение. Изменить поведение можно через параметр object_pairs_hook.


from collections import OrderedDict

json_str = '{"a": 1, "a": 2}'
# Сохранить все пары в OrderedDict
data = json.loads(json_str, object_pairs_hook=OrderedDict)
# data будет OrderedDict([('a', 1), ('a', 2)]) – дубликаты сохраняются
print(list(data.items()))
[('a', 1), ('a', 2)]

Предостережение: некоторые парсеры могут игнорировать дубликаты или выбрасывать ошибку. Если требуется строгое следование спецификации, используйте сторонние библиотеки.

Как сериализовать числа с плавающей точкой с фиксированной точностью?

По умолчанию float преобразуется в число с двойной точностью (double). Для округления можно использовать класс json.JSONEncoder с переопределением метода encode или передать default.


from decimal import Decimal, ROUND_HALF_UP
import json

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            # Округление до 2 знаков
            return float(obj.quantize(Decimal('0.00'), rounding=ROUND_HALF_UP))
        return super().default(obj)

data = {'price': Decimal('19.995')}
json_str = json.dumps(data, cls=DecimalEncoder)
print(json_str)  # {"price": 20.0}

Для более точного контроля используйте форматирование строки внутри энкодера.

Проблема: преобразование Decimal в float может привести к потере точности. Лучше сериализовать Decimal как строку с фиксированным числом знаков после запятой.

Как загрузить очень большой JSON файл без полного чтения в память?

Для обработки потоковых данных (JSON Lines) или очень больших массивов применяется инкрементальный парсер json.JSONDecoder с итерацией по файлу. Однако встроенный модуль не предоставляет полноценного потокового парсинга – для этого существуют сторонние библиотеки (ijson, orjson).


# Пример разбора JSON-массива построчно (JSON Lines)
with open('data.ndjson', 'r', encoding='utf-8') as f:
    for line in f:
        obj = json.loads(line)
        # обработка obj

Ограничение: стандартный json.loads загружает весь файл в память. Для гигабайтных данных нужно рассматривать стриминг.

Расширенные примеры и нестандартные сценарии

Пример 1: Кастомный кодировщик для поддержки объектов datetime, Decimal и других типов

Пример

import json
from datetime import datetime, date, time
from decimal import Decimal

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime, date)):
            return obj.isoformat()
        if isinstance(obj, time):
            return obj.strftime('%H:%M:%S')
        if isinstance(obj, Decimal):
            return str(obj)
        if isinstance(obj, set):
            return list(obj)
        if isinstance(obj, bytes):
            return obj.decode('utf-8')
        return super().default(obj)

data_to_serialize = {
    'now': datetime.now(),
    'today': date.today(),
    'meeting': time(14, 30),
    'price': Decimal('123.45'),
    'tags': {'python', 'json'},
    'data': b'hello'
}

json_string = json.dumps(data_to_serialize, cls=CustomEncoder, indent=2, ensure_ascii=False)
print(json_string)
{
  "now": "2025-04-03T12:00:00.123456",
  "today": "2025-04-03",
  "meeting": "14:30:00",
  "price": "123.45",
  "tags": ["json", "python"],
  "data": "hello"
}

Пример 2: Работа с JSON и объектами, содержащими циклические ссылки (использование default + ограничение глубины)

Пример

import json

class Node:
    def __init__(self, name):
        self.name = name
        self.child = None

root = Node('root')
child = Node('child')
root.child = child
child.child = root  # цикл

def safe_serialize(obj, seen=None):
    if seen is None:
        seen = {}
    if id(obj) in seen:
        return f'<circular reference to {obj.name}>'
    seen[id(obj)] = True
    if isinstance(obj, Node):
        return {'name': obj.name, 'child': safe_serialize(obj.child, seen)}
    return obj

try:
    result = json.dumps(safe_serialize(root), indent=2, ensure_ascii=False)
except Exception as e:
    print(f'Ошибка сериализации: {e}')
else:
    print(result)
{
  "name": "root",
  "child": {
    "name": "child",
    "child": "<circular reference to root>"
  }
}

Пример 3: Использование json.JSONDecoder для восстановления кастомных объектов из JSON

Пример

import json

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __repr__(self):
        return f'Person(name={self.name!r}, age={self.age})'

def person_decoder(dct):
    if '__person__' in dct:
        return Person(dct['name'], dct['age'])
    return dct

json_str = '{"__person__": true, "name": "Анна", "age": 28}'
obj = json.loads(json_str, object_hook=person_decoder)
print(obj)
print(type(obj))
Person(name='Анна', age=28)
<class '__main__.Person'>

Пример 4: Сериализация с сортировкой ключей и удалением лишних пробелов (минификация)

Пример

data = {'z': 1, 'a': {'b': 2, 'c': 3}, 'm': 4}
# Минифицированный JSON без пробелов
compact = json.dumps(data, separators=(',', ':'), sort_keys=True)
print(compact)
# Читаемый с отступами
pretty = json.dumps(data, indent=2, sort_keys=True)
print(pretty)
{"a":{"b":2,"c":3},"m":4,"z":1}
{
  "a": {
    "b": 2,
    "c": 3
  },
  "m": 4,
  "z": 1
}

Пример 5: Обработка JSON-строки с комментариями (нестандарт) с помощью простого фильтра

Пример

import re

def remove_json_comments(json_string):
    # Удаление однострочных комментариев //
    pattern = r'//[^\n]*'
    cleaned = re.sub(pattern, '', json_string)
    # Дополнительно можно удалить многострочные /* */
    cleaned = re.sub(r'/\*.*?\*/', '', cleaned, flags=re.DOTALL)
    return cleaned

json_with_comments = '''{
    // это комментарий
    "key": "value", /* еще комментарий */
    "num": 42
}'''
cleaned = remove_json_comments(json_with_comments)
data = json.loads(cleaned)
print(data)
{'key': 'value', 'num': 42}

Пример 6: Использование json.dumps с параметром default для обработки даты без написания полного энкодера

Пример

from datetime import datetime

def convert_dates(obj):
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    raise TypeError(f"Cannot serialize {type(obj)}")

data = {'created_at': datetime.now()}
result = json.dumps(data, default=convert_dates, indent=2)
print(result)
{
  "created_at": "2025-04-03T12:00:00.123456"
}

Работа с JSON в Python 3 - comments

En
Python 3 json (python)