Файл не сломан: json.dump() по умолчанию экранирует всю кириллицу, поэтому вместо «кофе» на диск попадает \u043a\u043e\u0444\u0435. Чтобы буквы остались буквами, надо повернуть две ручки сразу — ensure_ascii=False в самом json.dump() и encoding="utf-8" в open(); любой из них по отдельности не хватит, и ниже видно, чем именно заканчивается каждый неполный вариант.
Почему в json-файле python пишет русские буквы вместо текста коды?
Потому что у json.dump() и json.dumps() есть параметр ensure_ascii, и по умолчанию он равен True. В этом режиме модуль обязан выдать чистый ASCII, поэтому каждый символ за пределами ASCII заменяется на escape-последовательность вида \uXXXX. Кириллица попадает под правило целиком: «товар» становится \u0442\u043e\u0432\u0430\u0440, «Москва» — \u041c\u043e\u0441\u043a\u0432\u0430. Это документированное поведение стандартной библиотеки, а не сбой кодировки и не баг в твоём коде.
Вот тот самый случай. Обрати внимание: encoding="utf-8" здесь уже указан — и он не помогает.
import json
coffee = {"товар": "кофе 250 г", "цена": 349, "склад": "Москва"}
with open("prices.json", "w", encoding="utf-8") as f:
json.dump(coffee, f)
print(open("prices.json", encoding="utf-8").read())
{"\u0442\u043e\u0432\u0430\u0440": "\u043a\u043e\u0444\u0435 250 \u0433", "\u0446\u0435\u043d\u0430": 349, "\u0441\u043a\u043b\u0430\u0434": "\u041c\u043e\u0441\u043a\u0432\u0430"}
Такой файл не сломан: он читается обратно без потерь
Именно этот симптом люди ищут словами «python json русские буквы вместо текста коды» — и почти всегда сразу бегут его чинить. Сначала стоит убедиться, что чинишь ты именно баг. Юникод-последовательности \uXXXX — это часть спецификации JSON, а не мусор: любой корректный парсер обязан их развернуть. Проверь сам, скормив json.loads() ровно ту строку, которая лежит в файле.
import json
text = r'{"\u0442\u043e\u0432\u0430\u0440": "\u043a\u043e\u0444\u0435", "\u0446\u0435\u043d\u0430": 349}'
data = json.loads(text)
print(data)
print(data["товар"], data["цена"])
{'товар': 'кофе', 'цена': 349}
кофе 349
Строки вернулись целыми, ключи тоже. Значит, если файл читает только твоя же программа, менять вообще ничего не обязательно. Экранирование мешает ровно в двух местах: когда файл открывает человек глазами и когда его смотрит коллега в git-диффе. Оба повода уважительные — просто это вопрос удобства, а не корректности.
ensure_ascii=False отвечает за экранирование, а не за байты
Первая ручка живёт внутри модуля json и решает один-единственный вопрос: превращать ли не-ASCII символы в \uXXXX. К файлам и кодировкам она отношения не имеет — результат виден уже на строке в памяти, до всякой записи на диск.
import json
word = {"город": "Москва"}
print(json.dumps(word))
print(json.dumps(word, ensure_ascii=False))
{"\u0433\u043e\u0440\u043e\u0434": "\u041c\u043e\u0441\u043a\u0432\u0430"}
{"город": "Москва"}
Обе строки — обычные str, обе одинаково валидны как JSON, и обе разворачиваются в один и тот же словарь. Разница только в том, как текст выглядит. Базовый набор параметров сериализации собран в справочнике: JSON serialization.
encoding='utf-8' в open() отвечает за байты, а не за экранирование
Вторая ручка живёт вообще не в json, а в open(). Она решает, какими байтами твой текст ляжет на диск. Посмотри на файл в бинарном виде — там уже нет ни str, ни escape-последовательностей, только байты.
import json
word = {"город": "Москва"}
with open("city.json", "w", encoding="utf-8") as f:
json.dump(word, f, ensure_ascii=False)
print(open("city.json", "rb").read())
b'{"\xd0\xb3\xd0\xbe\xd1\x80\xd0\xbe\xd0\xb4": "\xd0\x9c\xd0\xbe\xd1\x81\xd0\xba\xd0\xb2\xd0\xb0"}'
Каждая русская буква заняла два байта — это и есть UTF-8. Латиница, кавычки и двоеточия остались однобайтовыми. Подробнее про пару «символы против байтов» — в разделе Text encoding and decoding.
Четыре комбинации двух настроек: таблица симптомов
Почти все форумные ответы останавливаются на одной из трёх верхних строк. Тебе нужна четвёртая.
ensure_ascii |
encoding в open() |
Что окажется в файле |
Симптом |
True (по умолчанию) |
не указан |
\u043a\u043e\u0444\u0435 |
Работает везде и никогда не падает, но глазами читать больно |
True (по умолчанию) |
"utf-8" |
\u043a\u043e\u0444\u0435 |
Тот самый случай: кодировку указали, а коды остались |
False |
не указан |
зависит от локали |
На английской Windows — UnicodeEncodeError, на русской — молчаливый файл в cp1251, на котором потом падает любой читатель, ждущий UTF-8 |
False |
"utf-8" |
кофе |
То, что нужно |
Нужны ещё и отступы — добавляй третий параметр: json.dump(data, f, ensure_ascii=False, indent=2) при open(..., encoding="utf-8"). Это и есть полный рецепт «записать русские символы в файл через json.dump».
Почему при записи json на Windows появляется UnicodeEncodeError: charmap codec?
Потому что повернули только одну ручку из двух. Если у open() не указан encoding, Python берёт кодировку из локали операционной системы: на Linux и macOS это почти всегда UTF-8, а на Windows — однобайтовая кодовая страница. На английской или европейской Windows это cp1252, где кириллицы попросту нет, поэтому кодек падает на первой же русской букве. Пока ensure_ascii был включён, проблемы не возникало: escape-последовательности состоят из чистого ASCII и влезают в любую кодовую страницу.
Воспроизвести это можно на любой ОС, если указать cp1252 явно:
import json
coffee = {"товар": "кофе", "цена": 349}
try:
with open("prices.json", "w", encoding="cp1252") as f:
json.dump(coffee, f, ensure_ascii=False)
except UnicodeEncodeError as e:
print(f"{type(e).__name__}: {e}")
UnicodeEncodeError: 'charmap' codec can't encode characters in position 1-5: character maps to <undefined>
Лечится одной правкой — encoding="utf-8" в open(). Узнать, какую кодировку подставляет твоя система, можно так: python -c "import locale; print(locale.getpreferredencoding(False))". Зеркальная беда случается при чтении: тот же перекос кодировок даёт либо UnicodeDecodeError, либо молчаливые кракозябры — это разобрано отдельно в статье про cp1251 и UTF-8 при чтении файлов.
Чем json.dump отличается от json.dumps и когда это важно?
json.dump() пишет прямо в файловый объект, а json.dumps() — с буквой s на конце, от string — возвращает готовую строку и никуда её не записывает. Параметр ensure_ascii у них одинаковый, а вот вторая ручка есть только у первого: кодировку задаёт тот open(), который ты передал в json.dump(). Если ты отдаёшь JSON в HTTP-ответ или кладёшь в очередь, файла нет вообще, остаётся только ensure_ascii, а за байты отвечает уже транспорт — например, заголовок Content-Type: application/json; charset=utf-8.
import json
user = {"имя": "Ольга", "город": "Казань"}
as_text = json.dumps(user, ensure_ascii=False)
print(type(as_text).__name__)
print(as_text)
with open("user.json", "w", encoding="utf-8") as f:
json.dump(user, f, ensure_ascii=False)
print(open("user.json", encoding="utf-8").read())
str
{"имя": "Ольга", "город": "Казань"}
{"имя": "Ольга", "город": "Казань"}
Как загрузить json-файл с кириллицей обратно?
При чтении никаких специальных настроек у модуля json нет: ensure_ascii — параметр только записи, а json.load() одинаково разбирает и экранированные последовательности, и живые русские буквы. Явно указывать стоит только encoding в open() — по той же причине, что и при записи. Отдельный случай — чужой файл из Excel, 1С или выгрузки с сайта: в самом его начале может стоять невидимый BOM, и тогда разбор падает.
import json
with open("from_1c.json", "wb") as f:
f.write('\ufeff{"склад": "Москва"}'.encode("utf-8"))
try:
with open("from_1c.json", encoding="utf-8") as f:
json.load(f)
except json.JSONDecodeError as e:
print(e)
with open("from_1c.json", encoding="utf-8-sig") as f:
print(json.load(f))
Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)
{'склад': 'Москва'}
Сообщение прямым текстом называет лекарство: encoding="utf-8-sig". При чтении этот кодек съедает BOM, если он есть, и ничего не меняет, если его нет, — поэтому для чужих входных файлов его можно ставить всегда. На записи он ведёт себя ровно наоборот: сам дописывает BOM в начало. Для своих json это лишнее — пиши с encoding="utf-8", иначе на твоём же файле споткнётся следующий json.load().
Когда экранирование всё-таки нужно
Иногда ensure_ascii=True — не помеха, а требование. Если JSON уезжает в старую систему, в лог с ASCII-фильтром, в поле базы с однобайтовой кодировкой или в канал, где никто не гарантирует UTF-8, экранированный вариант доедет без единой правки: в нём просто нет байтов старше 127.
import json
order = {"клиент": "Ольга", "сумма": 1290}
ascii_text = json.dumps(order)
utf8_text = json.dumps(order, ensure_ascii=False)
print(ascii_text.isascii(), utf8_text.isascii())
print(len(ascii_text), len(utf8_text))
print(json.loads(ascii_text) == json.loads(utf8_text))
True False
114 34
True
Цена вопроса видна в цифрах: экранированная запись втрое длиннее, зато состоит только из ASCII. И оба варианта разбираются в один и тот же словарь — выбор здесь про транспорт, а не про данные.
Частые ошибки
- Поставили
ensure_ascii=False, но не тронули open(). Кириллица перестала экранироваться, и теперь её надо чем-то закодировать: на английской Windows получаешь UnicodeEncodeError, на русской молча получаешь cp1251-файл. Добавь encoding="utf-8".
- Поставили
encoding="utf-8", но забыли ensure_ascii=False. Байты правильные, а в файле по-прежнему коды: экранирование случается раньше, и до кодировки живая кириллица просто не доезжает. Добавь ensure_ascii=False.
- Пробуют «расшифровать» файл через
decode("unicode_escape"). На чисто ASCII-строке приём выглядит рабочим, но стоит в данных появиться настоящей кириллице — он молча превращает её в мусор, потому что читает UTF-8-байты как latin-1.
import json
user = {"имя": "Ольга", "город": "Казань"}
text = json.dumps(user, ensure_ascii=False)
print(repr(text.encode("utf-8").decode("unicode_escape")))
'{"имÑ\x8f": "Ð\x9eлÑ\x8cга", "гоÑ\x80од": "Ð\x9aазанÑ\x8c"}'
Единственный правильный распаковщик — json.loads().
- Пишут в файл
str(словарь) вместо json.dump(). Русские буквы при этом выглядят прекрасно, а файл — не JSON: в нём одинарные кавычки.
import json
user = {"имя": "Ольга"}
with open("wrong.json", "w", encoding="utf-8") as f:
f.write(str(user))
print(open("wrong.json", encoding="utf-8").read())
try:
with open("wrong.json", encoding="utf-8") as f:
json.load(f)
except json.JSONDecodeError as e:
print(e)
{'имя': 'Ольга'}
Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
- Передают
ensure_ascii в json.load(). Ответ будет однозначный: TypeError: JSONDecoder.__init__() got an unexpected keyword argument 'ensure_ascii'. Параметр существует только на записи.
- Открывают файл в бинарном режиме
"wb" и отдают его в json.dump(). Получишь TypeError: a bytes-like object is required, not 'str': json.dump() пишет текст, значит режим должен быть "w" с указанной кодировкой.
- Файл записан правильно, а редактор всё равно показывает кракозябры. Тогда дело уже не в json, а в том, в какой кодировке файл открыли на просмотр.
Практика: сохрани и прочитай словарь с кириллицей без потерь
Итоговый рецепт — обе ручки плюс отступы, а затем проверка, что данные вернулись ровно теми же:
import json
catalog = [
{"название": "кофе Арабика", "цена": 349.0},
{"название": "чай Ассам", "цена": 219.5},
]
with open("catalog.json", "w", encoding="utf-8") as f:
json.dump(catalog, f, ensure_ascii=False, indent=2)
with open("catalog.json", encoding="utf-8") as f:
back = json.load(f)
print(back == catalog)
print(back[0]["название"])
True
кофе Арабика
Заметь последнюю строку: back — это список словарей, поэтому обращение идёт через back[0]["название"], а не back["название"]. Промах на этом месте даёт одну из самых частых ошибок при работе с разобранным JSON — list indices must be integers or slices, not str.
Чтобы работа со словарями осела в руках, а не только в голове, порешай прямо в браузере: «Разобрать строку настроек» — превращение текста в словарь, ровно та операция, ради которой обычно и берут json; «Сложи одинаковые товары» — свёртка списка словарей, типичная форма данных из json-выгрузки. Код проверяет настоящий Python 3.12, ставить ничего не надо.
Мини-резюме
- Коды вида
\u043a\u043e\u0444\u0435 в файле — не поломка: это валидный JSON, и json.load() вернёт исходные строки без потерь.
ensure_ascii=False в json.dump() и json.dumps() отключает экранирование, но ничего не знает про байты.
encoding="utf-8" в open() задаёт байты, но ничего не знает про экранирование.
- Нужны обе ручки:
json.dump(data, f, ensure_ascii=False, indent=2) при open(path, "w", encoding="utf-8").
UnicodeEncodeError: 'charmap' codec означает, что вторую ручку забыли и кодировку выбрала локаль Windows.
- При чтении хватает
encoding="utf-8", а для чужих файлов с BOM — encoding="utf-8-sig".
- Если JSON уезжает в ASCII-канал, оставляй
ensure_ascii=True: файл длиннее, зато проходит везде.
Файл не сломан:
json.dump()по умолчанию экранирует всю кириллицу, поэтому вместо «кофе» на диск попадает\u043a\u043e\u0444\u0435. Чтобы буквы остались буквами, надо повернуть две ручки сразу —ensure_ascii=Falseв самомjson.dump()иencoding="utf-8"вopen(); любой из них по отдельности не хватит, и ниже видно, чем именно заканчивается каждый неполный вариант.Почему в json-файле python пишет русские буквы вместо текста коды?
Потому что у
json.dump()иjson.dumps()есть параметрensure_ascii, и по умолчанию он равенTrue. В этом режиме модуль обязан выдать чистый ASCII, поэтому каждый символ за пределами ASCII заменяется на escape-последовательность вида\uXXXX. Кириллица попадает под правило целиком: «товар» становится\u0442\u043e\u0432\u0430\u0440, «Москва» —\u041c\u043e\u0441\u043a\u0432\u0430. Это документированное поведение стандартной библиотеки, а не сбой кодировки и не баг в твоём коде.Вот тот самый случай. Обрати внимание:
encoding="utf-8"здесь уже указан — и он не помогает.import json coffee = {"товар": "кофе 250 г", "цена": 349, "склад": "Москва"} with open("prices.json", "w", encoding="utf-8") as f: json.dump(coffee, f) print(open("prices.json", encoding="utf-8").read())Такой файл не сломан: он читается обратно без потерь
Именно этот симптом люди ищут словами «python json русские буквы вместо текста коды» — и почти всегда сразу бегут его чинить. Сначала стоит убедиться, что чинишь ты именно баг. Юникод-последовательности
\uXXXX— это часть спецификации JSON, а не мусор: любой корректный парсер обязан их развернуть. Проверь сам, скормивjson.loads()ровно ту строку, которая лежит в файле.import json # ровно то, что json.dump() записал в файл без ensure_ascii text = r'{"\u0442\u043e\u0432\u0430\u0440": "\u043a\u043e\u0444\u0435", "\u0446\u0435\u043d\u0430": 349}' data = json.loads(text) print(data) print(data["товар"], data["цена"])Строки вернулись целыми, ключи тоже. Значит, если файл читает только твоя же программа, менять вообще ничего не обязательно. Экранирование мешает ровно в двух местах: когда файл открывает человек глазами и когда его смотрит коллега в git-диффе. Оба повода уважительные — просто это вопрос удобства, а не корректности.
ensure_ascii=False отвечает за экранирование, а не за байты
Первая ручка живёт внутри модуля
jsonи решает один-единственный вопрос: превращать ли не-ASCII символы в\uXXXX. К файлам и кодировкам она отношения не имеет — результат виден уже на строке в памяти, до всякой записи на диск.import json word = {"город": "Москва"} print(json.dumps(word)) print(json.dumps(word, ensure_ascii=False))Обе строки — обычные
str, обе одинаково валидны как JSON, и обе разворачиваются в один и тот же словарь. Разница только в том, как текст выглядит. Базовый набор параметров сериализации собран в справочнике: JSON serialization.encoding='utf-8' в open() отвечает за байты, а не за экранирование
Вторая ручка живёт вообще не в
json, а вopen(). Она решает, какими байтами твой текст ляжет на диск. Посмотри на файл в бинарном виде — там уже нет ниstr, ни escape-последовательностей, только байты.import json word = {"город": "Москва"} with open("city.json", "w", encoding="utf-8") as f: json.dump(word, f, ensure_ascii=False) print(open("city.json", "rb").read())Каждая русская буква заняла два байта — это и есть UTF-8. Латиница, кавычки и двоеточия остались однобайтовыми. Подробнее про пару «символы против байтов» — в разделе Text encoding and decoding.
Четыре комбинации двух настроек: таблица симптомов
Почти все форумные ответы останавливаются на одной из трёх верхних строк. Тебе нужна четвёртая.
ensure_asciiencodingвopen()True(по умолчанию)\u043a\u043e\u0444\u0435True(по умолчанию)"utf-8"\u043a\u043e\u0444\u0435FalseUnicodeEncodeError, на русской — молчаливый файл в cp1251, на котором потом падает любой читатель, ждущий UTF-8False"utf-8"кофеНужны ещё и отступы — добавляй третий параметр:
json.dump(data, f, ensure_ascii=False, indent=2)приopen(..., encoding="utf-8"). Это и есть полный рецепт «записать русские символы в файл через json.dump».Почему при записи json на Windows появляется UnicodeEncodeError: charmap codec?
Потому что повернули только одну ручку из двух. Если у
open()не указанencoding, Python берёт кодировку из локали операционной системы: на Linux и macOS это почти всегда UTF-8, а на Windows — однобайтовая кодовая страница. На английской или европейской Windows это cp1252, где кириллицы попросту нет, поэтому кодек падает на первой же русской букве. Покаensure_asciiбыл включён, проблемы не возникало: escape-последовательности состоят из чистого ASCII и влезают в любую кодовую страницу.Воспроизвести это можно на любой ОС, если указать cp1252 явно:
import json coffee = {"товар": "кофе", "цена": 349} try: with open("prices.json", "w", encoding="cp1252") as f: json.dump(coffee, f, ensure_ascii=False) except UnicodeEncodeError as e: print(f"{type(e).__name__}: {e}")Лечится одной правкой —
encoding="utf-8"вopen(). Узнать, какую кодировку подставляет твоя система, можно так:python -c "import locale; print(locale.getpreferredencoding(False))". Зеркальная беда случается при чтении: тот же перекос кодировок даёт либоUnicodeDecodeError, либо молчаливые кракозябры — это разобрано отдельно в статье про cp1251 и UTF-8 при чтении файлов.Чем json.dump отличается от json.dumps и когда это важно?
json.dump()пишет прямо в файловый объект, аjson.dumps()— с буквой s на конце, от string — возвращает готовую строку и никуда её не записывает. Параметрensure_asciiу них одинаковый, а вот вторая ручка есть только у первого: кодировку задаёт тотopen(), который ты передал вjson.dump(). Если ты отдаёшь JSON в HTTP-ответ или кладёшь в очередь, файла нет вообще, остаётся толькоensure_ascii, а за байты отвечает уже транспорт — например, заголовокContent-Type: application/json; charset=utf-8.import json user = {"имя": "Ольга", "город": "Казань"} as_text = json.dumps(user, ensure_ascii=False) print(type(as_text).__name__) print(as_text) with open("user.json", "w", encoding="utf-8") as f: json.dump(user, f, ensure_ascii=False) print(open("user.json", encoding="utf-8").read())Как загрузить json-файл с кириллицей обратно?
При чтении никаких специальных настроек у модуля json нет:
ensure_ascii— параметр только записи, аjson.load()одинаково разбирает и экранированные последовательности, и живые русские буквы. Явно указывать стоит толькоencodingвopen()— по той же причине, что и при записи. Отдельный случай — чужой файл из Excel, 1С или выгрузки с сайта: в самом его начале может стоять невидимый BOM, и тогда разбор падает.import json # файл из чужой системы: перед { стоит невидимый BOM with open("from_1c.json", "wb") as f: f.write('\ufeff{"склад": "Москва"}'.encode("utf-8")) try: with open("from_1c.json", encoding="utf-8") as f: json.load(f) except json.JSONDecodeError as e: print(e) with open("from_1c.json", encoding="utf-8-sig") as f: print(json.load(f))Сообщение прямым текстом называет лекарство:
encoding="utf-8-sig". При чтении этот кодек съедает BOM, если он есть, и ничего не меняет, если его нет, — поэтому для чужих входных файлов его можно ставить всегда. На записи он ведёт себя ровно наоборот: сам дописывает BOM в начало. Для своих json это лишнее — пиши сencoding="utf-8", иначе на твоём же файле споткнётся следующийjson.load().Когда экранирование всё-таки нужно
Иногда
ensure_ascii=True— не помеха, а требование. Если JSON уезжает в старую систему, в лог с ASCII-фильтром, в поле базы с однобайтовой кодировкой или в канал, где никто не гарантирует UTF-8, экранированный вариант доедет без единой правки: в нём просто нет байтов старше 127.import json order = {"клиент": "Ольга", "сумма": 1290} ascii_text = json.dumps(order) utf8_text = json.dumps(order, ensure_ascii=False) print(ascii_text.isascii(), utf8_text.isascii()) print(len(ascii_text), len(utf8_text)) print(json.loads(ascii_text) == json.loads(utf8_text))Цена вопроса видна в цифрах: экранированная запись втрое длиннее, зато состоит только из ASCII. И оба варианта разбираются в один и тот же словарь — выбор здесь про транспорт, а не про данные.
Частые ошибки
ensure_ascii=False, но не тронулиopen(). Кириллица перестала экранироваться, и теперь её надо чем-то закодировать: на английской Windows получаешьUnicodeEncodeError, на русской молча получаешь cp1251-файл. Добавьencoding="utf-8".encoding="utf-8", но забылиensure_ascii=False. Байты правильные, а в файле по-прежнему коды: экранирование случается раньше, и до кодировки живая кириллица просто не доезжает. Добавьensure_ascii=False.decode("unicode_escape"). На чисто ASCII-строке приём выглядит рабочим, но стоит в данных появиться настоящей кириллице — он молча превращает её в мусор, потому что читает UTF-8-байты как latin-1.import json user = {"имя": "Ольга", "город": "Казань"} text = json.dumps(user, ensure_ascii=False) print(repr(text.encode("utf-8").decode("unicode_escape")))Единственный правильный распаковщик —
json.loads().str(словарь)вместоjson.dump(). Русские буквы при этом выглядят прекрасно, а файл — не JSON: в нём одинарные кавычки.import json user = {"имя": "Ольга"} with open("wrong.json", "w", encoding="utf-8") as f: f.write(str(user)) print(open("wrong.json", encoding="utf-8").read()) try: with open("wrong.json", encoding="utf-8") as f: json.load(f) except json.JSONDecodeError as e: print(e)ensure_asciiвjson.load(). Ответ будет однозначный:TypeError: JSONDecoder.__init__() got an unexpected keyword argument 'ensure_ascii'. Параметр существует только на записи."wb"и отдают его вjson.dump(). ПолучишьTypeError: a bytes-like object is required, not 'str':json.dump()пишет текст, значит режим должен быть"w"с указанной кодировкой.Практика: сохрани и прочитай словарь с кириллицей без потерь
Итоговый рецепт — обе ручки плюс отступы, а затем проверка, что данные вернулись ровно теми же:
import json catalog = [ {"название": "кофе Арабика", "цена": 349.0}, {"название": "чай Ассам", "цена": 219.5}, ] with open("catalog.json", "w", encoding="utf-8") as f: json.dump(catalog, f, ensure_ascii=False, indent=2) with open("catalog.json", encoding="utf-8") as f: back = json.load(f) print(back == catalog) print(back[0]["название"])Заметь последнюю строку:
back— это список словарей, поэтому обращение идёт черезback[0]["название"], а неback["название"]. Промах на этом месте даёт одну из самых частых ошибок при работе с разобранным JSON — list indices must be integers or slices, not str.Чтобы работа со словарями осела в руках, а не только в голове, порешай прямо в браузере: «Разобрать строку настроек» — превращение текста в словарь, ровно та операция, ради которой обычно и берут json; «Сложи одинаковые товары» — свёртка списка словарей, типичная форма данных из json-выгрузки. Код проверяет настоящий Python 3.12, ставить ничего не надо.
Мини-резюме
\u043a\u043e\u0444\u0435в файле — не поломка: это валидный JSON, иjson.load()вернёт исходные строки без потерь.ensure_ascii=Falseвjson.dump()иjson.dumps()отключает экранирование, но ничего не знает про байты.encoding="utf-8"вopen()задаёт байты, но ничего не знает про экранирование.json.dump(data, f, ensure_ascii=False, indent=2)приopen(path, "w", encoding="utf-8").UnicodeEncodeError: 'charmap' codecозначает, что вторую ручку забыли и кодировку выбрала локаль Windows.encoding="utf-8", а для чужих файлов с BOM —encoding="utf-8-sig".ensure_ascii=True: файл длиннее, зато проходит везде.