Почему JSON не парсится (Unexpected token) — 3 причины, и в JSON нет ни одной

Ты видишь в консоли что-то такое:
SyntaxError: Unexpected token ` in JSON at position 0
И идёшь искать ошибку в JSON. Не там ищешь.
Позиция 0 — это первый символ. Парсер споткнулся, не начав работу: он ещё не дошёл ни до кавычек, ни до запятых. Это значит одно — ему дали не JSON. Целиком не JSON, с самого начала.
Поэтому первое движение всегда одинаковое. Не вглядывайся в код — распечатай то, что парсишь:
console.log(JSON.stringify(raw.slice(0, 100)))
Именно JSON.stringify, а не просто console.log: он покажет невидимое — переносы, кавычки, пробелы. Первые сто символов почти всегда назовут виновника. А в сообщении об ошибке первым делом читай сам символ: он и есть подсказка. Разберём три случая, которые покрывают почти всё.
Причина 1. Модель обернула JSON в markdown
Самая частая, если в проекте есть ИИ. Ты просил модель вернуть JSON — она вернула. Заботливо, в блоке подсветки:
```json
{"name": "Аня", "age": 30}
```
Внутри — идеальный JSON. Снаружи — три обратные кавычки и слово json. Для JSON.parse это мусор с первого же символа. Отсюда и «Unexpected token `».
Модель не глючит. Её учили на markdown, где код принято оборачивать. Она делает то, что делают люди.
Как проверить: увидел в ошибке обратную кавычку — диагноз готов.
Как чинить. Костыль на сейчас — снять обёртку:
const clean = raw.trim().replace(/^```(?:json)?\s*/i, '').replace(/```$/i, '')
const data = JSON.parse(clean)
Работает, но это лечение симптома. Модель может обернуть иначе, добавить «Вот ваш JSON:» перед блоком, приписать пояснение после. Ты будешь чинить регулярку до конца жизни.
Настоящее решение — не просить JSON словами, а потребовать его на уровне API: структурированный вывод заставляет модель отдавать ответ строго по схеме. Тогда обёртки не будет в принципе — и парсить станет нечего.
Причина 2. Сервер прислал HTML вместо данных
Ошибка выглядит так:
SyntaxError: Unexpected token < in JSON at position 0
Угловая скобка в начале — это <!DOCTYPE html>. Тебе пришла веб-страница.
Что произошло: запрос ушёл не туда или упал, а сервер вежливо вернул человеческую страницу ошибки — 404 или 500. Твой код увидел ответ, обрадовался и попытался распарсить страницу как данные.
Как проверить: глянь в браузере вкладку «Сеть» — статус запроса и вкладку Response. Увидишь ту самую страницу.
Как чинить — не глотать ошибки молча:
const res = await fetch(url)
if (!res.ok) throw new Error(`${res.status} на ${url}`)
const ct = res.headers.get('content-type') ?? ''
if (!ct.includes('application/json')) {
throw new Error(`Ждали JSON, пришло ${ct}: ${(await res.text()).slice(0, 200)}`)
}
const data = await res.json()
Пять строк — и вместо загадочного Unexpected token < ты получаешь «404 на /api/usres». С опечаткой, которую видно сразу.
Рядом живёт близкий родственник — Unexpected token u. Буква u — это начало слова undefined: ты парсишь переменную, в которой ничего нет. Обычно потому, что забыл await. Это отдельная классика, разобранная тут: почему fetch возвращает undefined.
Причина 3. Это JSON, но по чужим правилам
Дошло до сюда — значит, тебе действительно прислали объект, и ошибка указывает не на позицию 0, а куда-то в середину. Теперь можно смотреть на сам текст.
JSON строже, чем кажется. Он не прощает того, что спокойно съедает JavaScript и любит вставлять текстовый редактор:
- Одинарные кавычки.
{'name': 'Аня'}— не JSON. Только двойные. - Висячая запятая.
{"a": 1,}— в JS законно, в JSON ошибка. - Умные кавычки.
{"name": "Аня"}с типографскими «ёлочками» вместо"— беда всех, кто копирует из документа или мессенджера. Глазами не отличить, парсер падает. - Комментарии. Их в JSON нет вообще.
Как проверить: ткни текст в любой валидатор JSON — он покажет строку и символ.
Как чинить: если JSON генерируешь ты — не собирай его строками, никогда. Собери объект и отдай JSON.stringify — он не забудет кавычку. Если JSON пришёл извне — виноват отправитель, чини у источника, а не регулярками у себя.
Порядок действий
Держи короткую шпаргалку:
- Распечатай первые 100 символов через
JSON.stringify. - Посмотри на символ из ошибки:
`→ обёртка модели,<→ HTML,u→ undefined. - Позиция 0 — проблема снаружи JSON. Не позиция 0 — теперь можно и в валидатор.
- Почини причину, а не текст. Обёртка — включи структурированный вывод. HTML — проверяй статус ответа. Кривые кавычки — генерируй через
stringify.
Главное, что стоит унести: JSON.parse почти никогда не врёт. Он честно говорит «это не JSON» — и обычно оказывается прав.
Почему модель добавляет ```json, если я прямо попросил «только JSON»?
Потому что просьба словами — это пожелание, а не гарантия. Модель предсказывает правдоподобный текст, а в текстах, на которых её учили, JSON почти всегда лежит в блоке кода. Иногда она послушается, иногда нет — почему модель игнорит инструкции. Гарантию даёт только схема на уровне API.
Стоит ли просто обернуть JSON.parse в try/catch?
Обязательно — но это защита, а не диагноз. try/catch спасает приложение от падения, однако в блоке catch обязательно логируй сам текст, который не распарсился. Иначе ты знаешь, что «что-то сломалось», и не знаешь ничего больше.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





