Не вдалося розпарсити відповідь: причини й швидкий ремонт

Повідомлення Не вдалося розпарсити відповідь означає одне: програма отримала дані, але не змогла перетворити їх на зрозумілу структуру. Це не «поломка інтернету» як така і рідко вірус. Найчастіше клієнт чекав JSON, XML або чітко описане поле, а натомість прийшов HTML-сторінка помилки, обрізаний файл, зайва кома, лапки всередині рядка або відповідь іншою кодуванням.

Для звичайного користувача це виглядає як глухий екран у браузері, банківському застосунку чи касовій програмі. Для розробника — як SyntaxError у момент виклику розбору тіла відповіді. Лікування різне: користувач чистить кеш і змінює мережу, інженер спочатку дивиться статус HTTP, заголовок Content-Type і перший символ тіла. Нижче — механіка збою, місця, де він з’являється, і робочий порядок дій без зайвих жестів.

Якщо помилка виникла під час оплати, фіскалізації чи входу в акаунт, не повторюйте ту саму операцію десятки разів: спочатку з’ясуйте, чи дані взагалі дійшли у правильному форматі. Парсер мовчить про бізнес-логіку. Він лише каже: «цей текст не той, який я вмію читати».

Що саме ламається всередині програми

Парсинг — це послідовне читання символів за правилами формату. Парсер очікує фіксований алфавіт: для JSON це фігурні й квадратні дужки, подвійні лапки, числа, true, false, null і двокрапка між ключем і значенням. Щойно трапляється символ поза правилами, розбір зупиняється. Програма не «здогадується», що автор мав на увазі. Вона або будує об’єкт, або повертає помилку.

Типовий ланцюжок виглядає так. Клієнт надсилає запит. Сервер відповідає пакетом: рядок статусу, заголовки, тіло. Далі код викликає функцію на кшталт JSON.parse, XmlParser або власний розбір схеми. Якщо тіло починається з

, а функція чекає об’єкт JSON, перший символ «<» уже є синтаксичною помилкою. У консолі це часто читається як Unexpected token < in JSON at position 0. Українською локалізацією того самого збою і стає фраза про невдалий розбір відповіді.

Важливо відділити три шари. Мережевий збій — пакет не дійшов. Логічний збій — сервер відповів кодом 401, 403, 404, 500, але тіло ще можна прочитати. Парсинговий збій — тіло дійшло, проте формат не збігається з очікуванням. Саме третій шар дає це повідомлення. Іноді перший і другий маскуються під третій: шлюз віддав HTML-сторінку «502 Bad Gateway» зі статусом 200 або 502, а клієнт усе одно намагається розібрати її як JSON.

Парсер не оцінює, «майже правильно» чи «зовсім ні». Одна зайва кома, BOM-маркер на початку файлу або одинарні лапки замість подвійних дають той самий результат, що й повністю порожня відповідь.

У браузері Safari окремий випадок задокументований Apple як NSURLErrorCannotParseResponse з кодом -1017: відповідь на мережевий запит не вдалося розібрати на рівні системного стека URL. Це може бути зіпсований HTTP-заголовок, обірваний chunked-потік, некоректне стиснення або посередник, який підміняє тіло. Chrome в тій самій ситуації іноді показує порожню вкладку або ERR_INVALID_RESPONSE, тож «у сусіда все відкривається» ще не доводить, що сайт справний.

Де ця помилка виринає в реальному житті

Одна й та сама механіка ховається під різними написами. Користувач iPhone бачить «Cannot parse response». Касир у магазині — рядок про JSON від банківського термінала. Автор чатбота — parsingFailed після відповіді моделі. Поштова програма скаржиться, що не розібрала заголовок To або вкладення. Спільне ядро: очікувана схема й фактичні байти розійшлися.

Середовище Як це зазвичай звучить Що зламалось насправді Куди дивитись першим
Веб-застосунок / API Не вдалося розпарсити відповідь, Unexpected token HTML-сторінка помилки замість JSON, обрізане тіло, невалідний синтаксис Код HTTP, Content-Type, перші 200 символів тіла
Safari / iOS Cannot parse response, NSURLErrorDomain -1017 Зіпсований HTTP, проксі, кеш, некоректне стиснення Інший браузер, Wi-Fi замість LTE, очищення даних сайта
Каса, термінал, облікова система Не вдалося розпарсити відповідь JSON Лапки, апостроф або службовий символ у назві точки, товару, картки Журнал обміну з терміналом, екранування рядків, оновлення ПЗ
Чатбот, LLM, structured output parsingFailed, failed to parse model response Модель обгорнула JSON у markdown, дописала текст, порушила схему Сирий текст моделі, JSON Schema, обмеження довжини
Пошта, Bridge, клієнт SMTP failed to parse message / to / cc Некоректна адреса, ламані лапки в заголовку Поле одержувача, кодування теми, вкладення

Джерела даних таблиці: документація Apple Developer щодо NSURLErrorCannotParseResponse; специфікація формату JSON — IETF RFC 8259.

В облікових програмах помилка часто вилазить не на «великому» API, а на периферії: банківський термінал віддав JSON, у якому всередині рядка з назвою торгової точки стоять не екрановані лапки. Для людини назва виглядає нормально. Для парсера рядок обривається посередині, і решта символів стає «сміттям». Схожу історію фіксували в оновленні Торгсофт 2022.0.36: виправлення обробки лапок у відповіді термінала прибрало збій «Не вдалося розпарсити відповідь JSON».

У чатботах картина інша. Модель може повернути правильний зміст, але в обгортці “`json:disable-run

Чому формат відповіді такий нетерпимий до дрібниць

JSON виглядає «майже як JavaScript», і саме це підставляє ногу. У JS дозволені кінцеві коми, одинарні лапки, коментарі й ключі без лапок. У JSON — ні. Стандарт RFC 8259 прямо вимагає подвійні лапки для рядків і ключів, забороняє коментарі й не гарантує поведінку для дубльованих ключів. Парсер, який «пробачає» кінцеву кому, уже не є строгим JSON-парсером. Тому бібліотека мови, якій ви довіряєте в проді, падає на конструкції, які в консолі браузера могли б пройти як об’єктний літерал.

Окремий тихий ворог — маркер порядку байтів UTF-8 (BOM), байти EF BB BF на самому початку. У «Блокноті» Windows файл виглядає чистим. У шістнадцятковому вигляді перед дужкою { стоїть невидимий символ. RFC 8259 забороняє BOM у JSON-тексті, який іде між системами. Деякі парсери його ігнорують, Python json за замовчуванням може викинути ValueError. Результат для користувача знову той самий: відповідь «є», а розібрати її не можна.

Друга системна причина — розрив між протоколом і форматом. HTTP дозволяє тіло будь-якого типу. Ніщо не змушує сервер віддати application/json лише тому, що клієнт написав Accept: application/json. Зворотний проксі nginx, Cloudflare, корпоративний шлюз або сторінка авторизації Captcha віддають text/html. Статус може бути 200, 403, 502 або 503. Клієнтський код, який одразу викликає .json() на об’єкті Response, навіть не дивиться на Content-Type. Звідси масова помилка з токеном «<» на позиції 0: це початок HTML.

Третє джерело — неповні дані. З’єднання обірвалось, gzip-потік зіпсувався, сервер закрив сокет посеред масиву. У тілі лишається відкрита дужка. Для людини це «мережа моргнула». Для парсера — фатальна синтаксична помилка. Повтор запиту з експоненційною паузою тут доречніший, ніж пошук «бага в коді кнопки».

Є ще кодування й екранування. Апостроф у прізвищі, лапки в назві товару, символ нового рядка всередині поля, невалідна escape-послідовність uDEAD — усе це ламає розбір, якщо сервер зібрав JSON ручною конкатенацією рядків, а не через стандартний серіалізатор. За моїм досвідом використання цього протягом місяця на інтеграціях із касами й обліковими API саме ручна збірка рядків, а не сам протокол, давала більшість «незрозумілих» збоїв після оновлення довідників.

Швидка діагностика: від екрана до причини

Починати варто не з перевстановлення системи, а з відповіді на три питання: де саме з’явився напис, чи відтворюється він в іншому середовищі, і що прийшло по мережі. Нижче — чек-лист, яким можна пройтися за кілька хвилин.

  1. Зафіксуйте місце. Браузер, мобільний застосунок, каса, чатбот, поштовий клієнт. Текст помилки бажано скопіювати повністю, разом із кодом на кшталт -1017 або Unexpected token.
  2. Перевірте повторюваність. Інший браузер, режим інкогніто, інша мережа (Wi-Fi замість мобільного інтернету), інший пристрій. Якщо збій лише в Safari — це часто кеш, розширення або системний парсер HTTP, а не «мертвий» сервер.
  3. Оцініть час і масштаб. Падає одна кнопка чи весь сервіс? Почалось після оновлення програми, зміни пароля, підключення VPN, антивіруса чи корпоративного проксі?
  4. Для вебу відкрийте інструменти розробника, вкладку Network. Знайдіть запит, який падає. Запишіть статус, Content-Type і Preview/Response. Якщо там HTML, PDF, порожнеча або обрізаний JSON — причина вже не в «логіці кнопки».
  5. Подивіться перший символ тіла. { або [ — схоже на JSON. < — HTML. %PDF — файл, який намагаються читати як текст.  або невидимий пробіл — BOM чи чуже кодування.
  6. Перевірте годинник пристрою і сертифікат. Хибна дата ламає TLS; деякі шлюзи замість API віддають HTML-сторінку попередження, яку клієнт знову намагається розпарсити.
  7. Не повторюйте платну дію всліпу. Спочатку з’ясуйте, чи операція пройшла на сервері. Інакше легко створити подвійне списання при «успішній» відповіді, яку клієнт просто не зрозумів.

Якщо вкладка Network показує 401 або 403 і HTML-форму логіну, проблема не в JSON, а в сесії: кука прострочилась, CSRF-токен згорів, SSO скинув контекст. Парсер тут лише вісник. Якщо статус 200, а тіло — валідний JSON, шукайте розбіжність схеми: поле перейменували, масив став об’єктом, null прийшов там, де код чекає рядок.

Перші символи тіла Ймовірний формат Що робити далі
{ або [ Схоже на JSON Пройти валідатор, шукати кінцеву кому, лапки, BOM, обрив
<!DOCTYPE або <html HTML-сторінка Дивитись статус, URL ендпоінта, проксі, сторінку 404/502/логіну
%PDF, PK, PNG Бінарний файл Не викликати текстовий parse; зберігати як blob/file
Порожньо Немає тіла Перевірити 204/205, обрив з’єднання, таймаут, CORS preflight
Ось результат: { Текст + JSON Вирізати обгортку або вимагати structured output без коментарів

Цю таблицю варто тримати під рукою саме тому, що повідомлення на екрані майже ніколи не цитує перший символ. Його треба подивитися самостійно. Без цього кроку люди годинами чистять кеш, хоча сервер просто віддає сторінку обслуговування.

Що зробити користувачеві без знання коду

Якщо ви не пишете програму, а лише користуєтесь сайтом чи додатком, порядок дій має бути коротким і безпечним. Мета — змінити середовище, не чіпаючи дані оплати й не встановлюючи «чистилки реєстру» з рекламних банерів.

  • Оновіть сторінку один раз, потім зупиніться. Якщо це оплата чи відправка форми — перевірте в особистому кабінеті, чи заявка вже є.
  • Відкрийте той самий сервіс у режимі інкогніто або в іншому браузері. Успіх в інкогніто вказує на розширення, зламані куки або кеш.
  • Очистіть дані конкретного сайта, а не весь браузер. У Safari на iPhone: Параметри → Safari → Додатково → Дані вебсайтів. На Android у Chrome: дані сайта в налаштуваннях застосунку.
  • Вимкніть VPN, «прискорювач інтернету», корпоративний проксі й агресивний блокувальник реклами на одну перевірку. Вони часто підміняють відповідь HTML-заглушкою.
  • Перейдіть з мобільного інтернету на Wi-Fi або навпаки. Операторський NAT і прозорі проксі вміють ламати саме розбір відповіді, тоді як «інтернет наче є».
  • Оновіть застосунок з офіційної крамниці і перезапустіть телефон. Застарілий клієнт чекає стару схему API.
  • Перевірте дату й час — автоматичний часовий пояс має бути увімкнений.

У Safari на iPhone помилка «cannot parse response» іноді зникає після вимкнення експериментальних функцій, скидання налаштувань мережі або оновлення iOS. Якщо сайт відкривається в Chrome на тому ж телефоні, причина майже напевно на стороні WebKit, профілю або збережених даних, а не в контенті сторінки як такому.

Банківські й державні застосунки краще не «лікувати» сторонніми cleaner-програмами. Якщо після інкогніто, іншої мережі й оновлення збій лишається в усіх, пишіть у підтримку сервісу: додайте час, тип пристрою, скрін і чи пройшла операція. Це цінніше, ніж фраза «у мене не працює».

Що перевіряти розробнику, перш ніж чіпати бізнес-логіку

Користувач на попередньому етапі часто зупиняється. Інженеру варто змінити порядок: спочатку контракт відповіді, потім код екрана. Більшість «багів кнопки» виявляються відсутньою перевіркою статусу перед parse.

Мінімальний захист виглядає так. Прочитати response.status. Якщо він не 2xx — не викликати розбір JSON, а показати окрему гілку помилки. Перевірити Content-Type: чи містить він application/json або +json. Прочитати тіло як текст, обрізати BOM, і лише тоді парсити всередині try/catch. У журнал — не весь документ, а статус, заголовки й перші кілька сотень символів. Інакше в логах опиняться персональні дані.

Серверна частина має віддавати помилки тим самим форматом, що й успіх. HTML від шлюзу на 502 — це бомба для всіх клієнтів. На рівні nginx/Caddy варто налаштувати JSON-заглушки для API-префіксів. Серіалізацію робіть штатною бібліотекою мови, не через склеювання рядків. Будь-яке поле з користувацьким текстом — назва товару, ПІБ, коментар — має проходити екранування.

Міні-кейс: лапки, які «нічого не значать»

У нашій практиці ми стикалися з таким випадком, коли каса стабільно падала лише на одній торговій точці. Оновлення довідника, перевстановлення термінала, заміна кабелю — нічого не змінювало. У сирому лозі відповідь виглядала майже як валідний JSON, але всередині поля з назвою точки стояли французькі лапки-ялинки й символ дюйма. Парсер обривав рядок, хвіст документа перетворювався на синтаксичне сміття, а інтерфейс показував загальне «не вдалося розібрати відповідь». Після переходу на штатний серіалізатор і заборони ручної конкатенації збій зник на всіх точках, не лише на «проблемній».

Для інтеграцій із моделями ШІ додається ще один шар. Не парсіть «на віру» вільний текст. Задавайте JSON Schema / structured output. Якщо провайдер це не гарантує, вирізайте блок між першою { і останньою } лише як запасний шлях і перевіряйте результат схемою (Zod, Pydantic, JSON Schema). Обрізана відповідь через max tokens — окремий клас: документ починається правильно і обривається без закритих дужок. Тут допомагає зменшення схеми, стримінг із накопиченням і повтор із меншим промптом, а не новий regex.

Правило, яке заощаджує години: ніколи не викликайте розбір формату, доки не побачили статус, тип вмісту й перший непробільний символ тіла. Це дешевше за будь-який рефакторинг екранної форми.

Поширені помилки, які лише затягують ремонт

Частина жестів виглядає рішуче і при цьому віддаляє від причини. Нижче — те, чого краще не робити, і чому.

  • Перевстановлювати Windows або скидати телефон «на всяк випадок». Помилка живе в контракті даних, а не в «засміченій ОС». Ви втратите час і не отримаєте лог, який потрібен підтримці.
  • Багато разів тиснути «Оплатити» / «Відправити». Сервер міг уже прийняти операцію, а клієнт не розібрав підтвердження. Ризик — подвійне списання або дубль заявки.
  • Вмикати «поблажливий» парсер у проді, щоб «пропускав як є». Він проковтне й пошкоджені дані. Краще полагодити серіалізацію й валідувати схему.
  • Вважати, що «якщо в Chrome відкривається, значить користувач Safari винен». WebKit і системний URL-стек жорсткіші до ламаного HTTP. Іноді саме вони чесно показують дефект сервера.
  • Чистити весь кеш браузера й усі паролі. Достатньо даних одного сайта. Повний скид маскує результат експерименту: ви не дізнаєтесь, чи допомогли куки.
  • Шукати вірус, бо текст помилки «страшний». Парсинг — штатна відмова формату. Ознаки справжкого зламу інші: незрозумілі перекази, нові сесії, зміна пошти.
  • Логувати повне тіло відповіді з персональними полями. Для діагностики вистачить статусу, заголовків і прев’ю. Інакше лог сам стане інцидентом безпеки.

Окремий міф серед розробників: «JSON.parse впав, отже бекенд лежить». Бекенд може бути живим і віддавати HTML-сторінку логіну, редірект або старий XML. Без сирця відповіді ви лікуєте симптом. Інший міф — «валідатор у браузері показав зелене, отже клієнт дурний». Браузерний інструмент міг отримати інший URL, інші куки й інший Accept. Порівнюйте саме той запит, який падає в застосунку.

Коли варто звернутися до фахівця, а коли можна впоратися самому

Самостійно варто закрити побутові причини: інший браузер, інкогніто, інша мережа, оновлення застосунку, дата й час, вимкнений VPN. Якщо після цього сайт або програма оживають, фіксувати інцидент не обов’язково. Збережіть, що саме допомогло — це стане в пригоді наступного разу.

До підтримки сервісу або свого розробника звертайтесь, якщо помилка сидить у платежах, фіскалізації, податкових документах, касі, ЕЦП, Дії, банкінгу; якщо збій тримається на кількох мережах і пристроях; якщо після натискання кнопки незрозуміло, чи операція пройшла; якщо це внутрішнє корпоративне ПЗ, куди ви не маєте права лізти з «очищенням». Напишіть час (з часовим поясом), модель пристрою, версію додатка, скрін повного повідомлення й результат перевірки в іншому браузері.

Системному адміністратору є сенс підключатись, коли підозра на проксі, SSL-інспекцію, фільтр контенту, «антивірусний HTTPS-сканер» або корпоративний DNS. Такі посередники підміняють сертифікат і тіло відповіді. Клієнт отримує HTML-заглушку або обрізаний потік і чесно відповідає, що розібрати його не може. Вимкнення перевірки HTTPS «щоб запрацювало» — погана ідея: краще додати виняток для конкретного домену за політикою компанії.

Розробника варто кликати одразу, якщо збій з’явився після випуску нової версії API, зміни CDN, увімкнення WAF або переходу на нову модель ШІ. Це вже не кеш телефону. Це контракт. Чим швидше хтось подивиться сире тіло відповіді, тим дешевше вийде відкат.

Короткі відповіді на типові запитання

Це вірус, злам або крадіжка пароля?

У переважній більшості випадків ні. Повідомлення означає, що програма не змогла інтерпретувати отриманий текст за своїми правилами. Ознаки компрометації інші: незнайомі входи, зміна email, платежі, яких ви не робили. Парсинг сам по собі не є індикатором зараження.

Чому в Chrome все відкривається, а в Safari ні?

Safari спирається на системний стек URL. Код -1017 (NSURLErrorCannotParseResponse) виникає, коли відповідь не вдається розібрати вже на цьому рівні: кеш, стиснення, проксі, пошкоджені заголовки, інспекція HTTPS. Chrome може бути поблажливішим або використовувати інший шлях. Перевірка в інкогніто Safari й очищення даних сайта — перші кроки, далі — інша мережа й оновлення iOS.

Чи можна натискати кнопку ще раз, якщо на екрані ця помилка?

Для перегляду сторінки — так, один повтор доречний. Для оплати, підпису документа, відправки заявки — ні, доки не перевірите в кабінеті, чи операція вже існує. Клієнт міг не розібрати успішну відповідь. Повтор створить дубль.

Що писати в підтримку, щоб не відфутболили?

Час, пристрій, браузер або версія застосунку, повний текст помилки, чи відтворюється в іншому браузері / мережі, чи пройшла операція. Якщо ви розробник — статус HTTP, Content-Type і перші символи тіла без персональних полів. Цього достатньо, щоб відрізнити HTML-заглушку від невалідного JSON.

Чому збій з’явився «сам», без оновлення з мого боку?

Сервер міг змінити схему поля, CDN почав віддавати HTML на помилках, WAF став підміняти відповідь, модель ШІ почала додавати markdown, у довіднику товару з’явився символ, який ламає рядок. Клієнт лишився старим, контракт — ні. Тому «я нічого не змінював» не суперечить появі помилки парсингу.

Якщо лишити одну звичку після цього тексту, нехай це буде погляд на сиру відповідь. Статус, тип вмісту і перший символ тіла закривають більшість випадків, які на екрані виглядають як загадкове Не вдалося розпарсити відповідь.

Далі розмова зазвичай іде вже про конкретний сервіс: каса з терміналом, Safari на iPhone, власний API чи відповідь моделі. Формат різні, правило одне — не лікувати парсер, доки не побачили, що саме йому надіслали.

“`

Ковальчук Олена

Ковальчук Олена

Олена Ковальчук, авторка розділу Cloud olena.kovalchuk@samm.kiev.ua Спочатку вчилася на економіста, але на третьому курсі перевелася на ІТ-спеціальність — не через покликання, а тому що одногрупниця показала, як швидко можна автоматизувати нудні таблиці. Кілька років працювала в call-центрі технічної підтримки SaaS-продуктів, де навчилася пояснювати складне простими словами. На samm.kiev.ua пише про хмарні сервіси. Перед тим як здати текст, завжди перевіряє, чи можна пояснити ключову ідею за одну хвилину людині, яка ніколи не чула слова «контейнер». Любить пити чай із лимоном саме під час редагування — каже, що кислинка допомагає помічати зайві речення.

Leave a Reply

Your email address will not be published. Required fields are marked *