Почему ПрочитатьJSON спотыкается на первом же символе и как это починить
Содержание
Ошибка «непредвиденный символ при чтении JSON» в 1С почти всегда значит одно: в ПрочитатьJSON пришёл не JSON или JSON с мусором в начале. Чаще всего это невидимый маркер BOM (символ с кодом 65279), HTML-страница ошибки вместо ответа API или текст в неверной кодировке. Исправление — посмотреть первые символы строки, убрать лишнее и читать данные в правильной кодировке.
Платформа не «ломается» и не теряет данные: парсер ЧтениеJSON строгий и останавливается на первом символе, который не вписывается в грамматику JSON. Формулировка сообщения немного отличается в разных версиях платформы, обычно в нём есть номер строки и позиция. Позиция 1 — проблема в самом начале текста, большая позиция — испорчено содержимое.
Откуда берётся непредвиденный символ в JSON
Парсер ждёт в начале { или [ (реже строку или число), а внутри — только правильно экранированные значения. Всё остальное он считает ошибкой синтаксического анализа. Источники мусора на практике такие:
- 🔹 BOM в начале текста. Файл или ответ сохранён в UTF-8 с маркером порядка байт
EF BB BF. После декодирования в строку это невидимый символ с кодом 65279, и разбор падает на позиции 1. - 🌐 HTML вместо JSON. Сервер вернул страницу 404, 502, форму авторизации или заглушку прокси. Первый символ —
<. - 📭 Пустой ответ. Код 204, обрыв соединения, таймаут. Строка пустая или из одних пробелов.
- 🔤 UTF-16. Тело пришло в двухбайтовой кодировке, а читаете вы как UTF-8. Между буквами оказываются нулевые байты.
- 📦 Сжатое тело. В ответе
Content-Encoding: gzip, а до программы дошли сырые байты. Первые байты1F 8B. - 🧩 JSONP и обёртки. Ответ вида
callback({...})или)]}'в первой строке — так некоторые сервисы защищаются от встраивания. - ✂️ «Почти JSON». Одинарные кавычки, запятая после последнего элемента, комментарии
//, значенияNaNиundefined. Браузер такое иногда прощает, 1С — нет. - ⛔ Неэкранированные управляющие символы. Табуляция или перевод строки внутри строкового значения без
\tи\n. Типично для JSON, склеенного вручную конкатенацией строк. - 📚 Несколько объектов подряд. Формат NDJSON (один объект на строку) или два ответа, склеенные в один файл. Первый объект читается, на втором — ошибка.
Расхождение Windows-1251 и UTF-8 обычно даёт не эту ошибку, а «кракозябры» в русских значениях. Служебные символы JSON — латиница, и они одинаковы в обеих кодировках. Если ошибка есть, а русский текст читается нормально, ищите BOM, обёртку или управляющие символы.
Как найти символ, на котором падает разбор
Не гадайте по тексту ошибки: выведите, что на самом деле пришло. Понадобятся три вещи: код ответа, заголовок типа содержимого и коды первых символов.
Ответ = Соединение.Получить(Запрос);
Тело = Ответ.ПолучитьТелоКакСтроку("UTF-8");
Сообщить("Код ответа: " + Формат(Ответ.КодСостояния, "ЧГ=0"));
Сообщить("Content-Type: " + Строка(Ответ.Заголовки.Получить("Content-Type")));
Сообщить("Длина: " + Формат(СтрДлина(Тело), "ЧН=0; ЧГ=0"));
Для Н = 1 По Мин(10, СтрДлина(Тело)) Цикл
Сообщить(Формат(Н, "ЧГ=0") + ": «" + Сред(Тело, Н, 1) + "» код "
+ Формат(КодСимвола(Тело, Н), "ЧГ=0"));
КонецЦикла;
Ключи в Заголовки чувствительны к регистру. Если Content-Type вернулся пустым, попробуйте content-type — часть серверов пишет заголовки строчными буквами.
Для разбора на уровне байтов сохраните тело как есть, без декодирования:
ДД = Ответ.ПолучитьТелоКакДвоичныеДанные();
ДД.Записать(КаталогВременныхФайлов() + "answer.json");
Сообщить(Лев(ПолучитьHexСтрокуИзДвоичныхДанных(ДД), 40));
Файл откройте в редакторе, который показывает кодировку и спецсимволы. Шестнадцатеричная строка сразу покажет BOM (EFBBBF), UTF-16 (FFFE или FEFF и нули через байт) или gzip (1F8B).
Если позиция ошибки большая, вырежьте фрагмент вокруг неё: Сред(Тело, Позиция - 50, 100). Учтите, что в многострочном JSON позиция может отсчитываться от начала строки, а не от начала текста — тогда сначала найдите нужную строку по номеру через СтрПолучитьСтроку.
Очистка текста перед парсингом JSON
Чистить имеет смысл только то, что нельзя исправить у источника. Функция ниже снимает типичные «наслоения» и не трогает данные внутри значений:
Функция ПодготовитьТекстJSON(Знач Текст) Экспорт
// BOM после декодирования — символ с кодом 65279
Если СтрДлина(Текст) > 0 И КодСимвола(Текст, 1) = 65279 Тогда
Текст = Сред(Текст, 2);
КонецЕсли;
Текст = СокрЛП(Текст);
// Обёртка JSONP: callback({...});
Первый = Лев(Текст, 1);
Если Первый <> "{" И Первый <> "[" Тогда
Начало = СтрНайти(Текст, "(");
Конец = СтрНайти(Текст, ")", НаправлениеПоиска.СКонца);
Если Начало > 0 И Конец > Начало Тогда
Текст = СокрЛП(Сред(Текст, Начало + 1, Конец - Начало - 1));
КонецЕсли;
КонецЕсли;
// Управляющие символы, которых в корректном JSON быть не может
Для Код = 1 По 31 Цикл
Если Код = 9 Или Код = 10 Или Код = 13 Тогда
Продолжить;
КонецЕсли;
Текст = СтрЗаменить(Текст, Символ(Код), "");
КонецЦикла;
Первый = Лев(Текст, 1);
Если Первый <> "{" И Первый <> "[" Тогда
ВызватьИсключение "Ответ не похож на JSON: " + Лев(Текст, 200);
КонецЕсли;
Возврат Текст;
КонецФункции
Что функция делает намеренно не так, как хочется:
- Не удаляет табуляцию и переводы строк. Между элементами они законны. Внутри строкового значения — нет, но отличить одно от другого без полного разбора нельзя. Такой JSON надо чинить у отправителя.
- Не превращает одинарные кавычки в двойные. Замена сломает значения, в которых апостроф — часть текста.
- Падает с понятным сообщением, если после очистки текст не начинается с
{или[. Это лучше, чем «непредвиденный символ» без контекста.
Не вырезайте всё до первой { «на всякий случай». Если пришла HTML-страница с примером JSON внутри, вы молча разберёте не те данные.
NDJSON (по объекту на строку) не очищают, а читают построчно: каждую непустую строку отдельно через УстановитьСтроку и ПрочитатьJSON, результаты складывают в массив.
Кодировка и потоки: как читать JSON без сюрпризов
Самая частая причина ошибки при загрузке из файла и HTTP — двойное декодирование: сначала байты превращают в строку с неверной кодировкой, потом строку отдают парсеру. Надёжнее отдавать ЧтениеJSON файл или поток и явно указывать кодировку.
| Источник | Как читать | Кодировка |
|---|---|---|
| Файл на диске | Чтение.ОткрытьФайл(ИмяФайла, "UTF-8") | вторым параметром |
| Двоичные данные | Чтение.ОткрытьПоток(ДД.ОткрытьПотокДляЧтения(), "UTF-8") | вторым параметром |
| Тело HTTP-ответа | Ответ.ПолучитьТелоКакСтроку("UTF-8") → УстановитьСтроку | при получении строки |
| Готовая строка | Чтение.УстановитьСтроку(Текст) | уже декодирована |
Пример для файла в Windows-1251, который выгрузила старая программа:
Чтение = Новый ЧтениеJSON;
Чтение.ОткрытьФайл(ИмяФайла, "windows-1251");
Данные = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
Если кодировку источника вы не знаете, получите двоичные данные и посмотрите первые байты, как в разделе про диагностику. ПолучитьСтрокуИзДвоичныхДанных(ДД, КодировкаТекста.UTF8) даёт строку с явной кодировкой, дальше — функция очистки.
Второй параметр Истина в ПрочитатьJSON возвращает Соответствие вместо Структуры. Это не про символы, но спасает от соседней ошибки: ключ JSON с пробелом, дефисом или цифрой в начале нельзя сделать именем свойства структуры.
Не путайте ПрочитатьJSON и СериализаторXDTO.ПрочитатьJSON. Второй ждёт JSON в формате сериализации самой 1С — со служебными полями #type и #value. Произвольный ответ стороннего API через XDTO не прочитается.
Попытка-Исключение и журнал регистрации при ошибках чтения JSON
Ошибка разбора в регламентном задании или обмене без обработки обрывает весь процесс. Оборачивайте чтение в Попытка и пишите в журнал регистрации не только текст ошибки, но и начало ответа — без него через неделю причину не восстановить.
Чтение = Новый ЧтениеJSON;
Попытка
Чтение.УстановитьСтроку(ПодготовитьТекстJSON(Тело));
Данные = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Обмен.ЗагрузкаJSON",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(Инфо) + Символы.ПС
+ "Код ответа: " + Формат(Ответ.КодСостояния, "ЧГ=0") + Символы.ПС
+ "Начало ответа: " + Лев(Тело, 500));
Данные = Неопределено;
КонецПопытки;
Чего в логе быть не должно:
- 🔑 токенов, паролей и заголовков авторизации — журнал читают многие;
- 📄 полного тела мегабайтного ответа — 500 символов хватает для диагноза, остальное раздувает журнал.
Найти записи потом просто: Администрирование → Обслуживание → Журнал регистрации, отбор по событию Обмен.ЗагрузкаJSON и уровню «Ошибка». Имя события придумайте своё, но одно на весь обмен.
Внешние API и HTTP-сервисы: ошибки JSON на стыке систем
1С получает данные
- Проверяйте код ответа до разбора. Разбирайте тело только при кодах 200–299. При 401, 403, 500 и прочих пишите в лог код и начало тела — там почти всегда HTML или текстовое сообщение сервера.
- Смотрите
Content-Type. Если тамtext/html, разбирать нечего. Еслиapplication/json; charset=...— берите кодировку оттуда. - Учитывайте прокси и балансировщики. Страница «502 Bad Gateway» от nginx приходит в HTML, даже если сам сервис всегда отвечает JSON.
- Задайте таймаут. Параметр
ТаймаутуHTTPСоединениеспасает от обрезанного ответа на медленном канале. Обрезанный JSON падает с ошибкой где-то в конце текста.
1С принимает запрос в своём HTTP-сервисе
Если ошибка возникает в модуле HTTP-сервиса при разборе входящего запроса, проблема обычно в клиенте, но защищаться нужно на своей стороне:
- 📨 Клиент шлёт форму, а не JSON. Тело вида
name=value&id=5приходит с типомapplication/x-www-form-urlencoded. Проверяйте заголовокContent-Typeвходящего запроса и отвечайте кодом 415 с понятным текстом. - 🔤 Клиент не указал кодировку. Читайте тело с явной кодировкой:
Запрос.ПолучитьТелоКакСтроку("UTF-8"), а не без параметра. - 🧪 Тестовый запрос из Postman или curl. Частая ловушка — одинарные кавычки вокруг тела в командной строке Windows, которые уходят внутрь JSON. Сначала повторите запрос из файла с телом.
При ошибке разбора возвращайте клиенту код 400 и короткое сообщение с позицией. Так отправитель найдёт свой дефект сам, а не будет писать вам в поддержку.
1С отправляет данные или отвечает как HTTP-сервис
Здесь ошибку ловит другая сторона, но причина у вас. Чаще всего это BOM в теле, который добавляет сама 1С, и JSON, собранный конкатенацией строк.
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, ДанныеОтправки);
ТекстJSON = Запись.Закрыть();
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(ТекстJSON, КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
Тот же третий параметр есть у УстановитьТелоИзСтроки для ответа HTTP-сервиса. Если внешняя система жалуется на «unexpected token» в позиции 0 — это почти наверняка BOM, и этот параметр его убирает.
Не собирайте JSON сложением строк. ЗаписатьJSON сам экранирует кавычки, обратные слэши и переводы строк. Ручная склейка ломается на первом же наименовании вроде ООО "Ромашка" или комментарии из двух строк.
Частые вопросы
Почему тот же файл открывается в браузере, а 1С пишет ошибку?
Ошибка появилась после обновления платформы, хотя код не меняли. Что проверить?
Можно ли прочитать JSON с комментариями, например файл настроек?
Ошибка возникает только на некоторых записях из большой выгрузки. Как найти виновника?
Что делать, если ответ огромный и строка не помещается в память?
ОткрытьПоток и читайте последовательно методом Прочитать объекта ЧтениеJSON, обрабатывая элементы по одному.