JSON в Python: эффективные приемы сериализации и десериализации
Основные приемы работы с 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"
}