2025, Nov 01 13:16
Как правильно передавать JSON в requests: dict, json.dumps и альтернативы
Почему не стоит собирать JSON строками в Python. Как передавать данные в requests через dict и json.dumps и когда использовать params, data или json без ошибок.
Когда вы формируете JSON‑тело для requests в Python и пытаетесь подставлять переменные в строку в тройных кавычках, поначалу это кажется удобным. На деле такой подход хрупкий: его легко сломать, он разваливается при малейшем изменении структуры или при появлении символов, требующих экранирования. Типичный сценарий: нужно сделать поля MAC и NAME динамическими при обходе CSV. Правильнее собирать данные как объекты Python и сериализовать их — без ручной разметки JSON.
Пример проблемы
Вот шаблон, который часто вызывает проблемы при попытке превратить переменные в JSON‑тело:
payload = '''{
"clients": [
{
"mac": HW_MAC,
"name": HOST_ALIAS
},
],}'''
С виду это «как будто JSON», но на самом деле он невалидный, а заполнители не будут восприняты как переменные. К тому же, висячие запятые и неверные скобки ломают разбор.
Что именно идет не так
Ручное форматирование JSON легко приводит к ошибкам. Запятые после последнего элемента недопустимы и вызывают ошибки парсинга. Если попытаться подставлять переменные через f‑строки, фигурные скобки придется удваивать, чтобы их не восприняли как маркеры форматирования. И главное — интерполяция строк не выполнит нужное экранирование: значения с кавычками или спецсимволами дадут невалидный результат. Сериализатор делает это правильно и стабильно.
Решение: соберите объект Python и сериализуйте через json.dumps
Создайте обычный dict или list, затем преобразуйте его в JSON‑строку с помощью json.dumps. Такой подход сохраняет типы данных при обходе, а сериализатор гарантирует валидный JSON и корректное экранирование.
import json
HW_MAC = 'xxxxxxxxxxxx'
HOST_ALIAS = 'example'
request_body = json.dumps({
"clients": [
{"mac": HW_MAC, "name": HOST_ALIAS}
]
}, indent=2)
print(request_body)
Результат:
{
"clients": [
{
"mac": "xxxxxxxxxxxx",
"name": "example"
}
]
}
Идя по строкам CSV, присваивайте текущие значения переменным вроде HW_MAC и HOST_ALIAS, заново собирайте dict и снова сериализуйте. Этого достаточно — никакой хрупкой конкатенации и ручных кавычек.
А нужна ли вам вообще JSON‑строка?
В зависимости от метода и эндпоинта HTTP, возможно, вообще не нужно ничего превращать в строку. Например, GET передает параметры через params, а PUT может отправлять form‑encoded данные через data. В обоих случаях requests сам занимается кодированием.
import requests
query_args = {"key1": "value1", "key2": "value2"}
resp = requests.get("https://httpbin.org/get", params=query_args)
import requests
resp = requests.put("https://httpbin.org/put", data={"key": "value"})
Можно передать dict через аргумент json=, и requests сам сериализует его в JSON и автоматически выставит заголовок Content-Type.
import requests
resp = requests.get("https://httpbin.org/get", json={"some": "data"})
Почему это важно
Поручив сериализацию специализированному инструменту, вы избавляетесь от целого класса скрытых ошибок. Он следит за корректной структурой, не допускает висячих запятых и правильно экранирует все нужные символы внутри строк. В итоге код чище и его проще поддерживать, когда формат полезной нагрузки меняется.
Итоги
Перестаньте собирать JSON‑строки вручную для requests. Используйте нативные структуры Python и json.dumps, когда действительно нужна JSON‑строка, либо передавайте данные напрямую через params, data или json — в зависимости от задачи. Так полезные нагрузки остаются валидными, намерения — прозрачными, а код — устойчивым при работе с динамическими значениями вроде MAC и NAME.
Статья основана на вопросе на StackOverflow от Joseph Jenkins и ответе от Mark Tolonen.