خطای Unexpected End of JSON Input یعنی چه و چطور رفعش کنیم؟
پارسر پیش از رسیدن به یک مقدار کامل به انتهای متن رسیده است؛ و در بیشتر موارد دلیلش این است که اصلاً متنی وجود نداشته.
JSON.parse() یا response.json() را صدا میزنید و با SyntaxError: Unexpected end of JSON input روبهرو میشوید. برخلاف خطای unexpected token، این خطا به نویسه اشتباهی اشاره نمیکند. معنایش این است که پارسر به آخرین نویسه رسیده، در حالی که هنوز منتظر یک مقدار بوده است. پس دو حالت بیشتر وجود ندارد: یا متن خالی بوده، یا وسط یک سند قطع شده است. تمام کار رفع خطا تشخیص همین دو حالت از یکدیگر است و برای آن یک خط لاگ کافی است.
یک خطا در محیطهای مختلف
| کجا میبینید | پیام |
|---|---|
کروم، اج و Node.js، تابع JSON.parse | Unexpected end of JSON input |
کروم و اج، متد response.json() | همان پیام، گاهی با پیشوند Failed to execute 'json' on 'Response': |
کروم، پاسخی که هیچ بدنهای ندارد، مثل پاسخ مات (opaque) در حالت no-cors | Unexpected end of input |
| فایرفاکس | JSON.parse: unexpected end of data at line 1 column 1 of the JSON data |
| سافاری | JSON Parse error: Unexpected EOF |
پایتون، تابع json.loads | Expecting value: line 1 column 1 (char 0) |
وقتی پیام از خط ۱، ستون ۱ یا نویسه ۰ حرف میزند، یعنی پارسر اصلاً چیزی برای خواندن پیدا نکرده است. متن خالی JSON معتبر نیست: کوتاهترین سندهای معتبر، مقدارهایی مثل 0، null، "" یا {} هستند.
علت معمول: بدنه خالی یا رشته خالی
پیش از اینکه دنبال ایرادی در داده بگردید، بررسی کنید اصلاً دادهای وجود داشته است یا نه. اینها منابع رایج رشته خالیاند:
- پاسخ ۲۰۴ (No Content). بسیاری از APIها به یک
DELETEیاPUTموفق، با کد وضعیت ۲۰۴ و بدون بدنه جواب میدهند.res.okبرای ۲۰۴ برابرtrueاست، پس بررسی آن از فراخوانیres.json()که بعدش میآید محافظت نمیکند. - درخواستهای HEAD. پاسخ به
HEADهیچوقت بدنه ندارد، هدرهایش هرچه بگویند. - fetch با
mode: 'no-cors'. این گزینه محدودیت CORS را دور نمیزند. نتیجهاش یک پاسخ مات (opaque) با کد وضعیت ۰ و بدنهای است که کد شما اجازه خواندنش را ندارد، پس پارس کردنش همیشه شکست میخورد. CORS را روی سرور فعال کنید یا API را از طریق بکاند خودتان صدا بزنید. - پاسخ ۲۰۰ یا ۵۰۰ بدون هیچ محتوا. هندلری که هیچوقت خروجیاش را نمینویسد، یا خطای سروری که درخواست را پیش از ارسال هر چیزی تمام میکند، بدنهای خالی با یک کد وضعیت معمولی تولید میکند.
- مقدارهای ذخیرهشده و فایلهای خالی.
localStorage.setItem('cart', '')، یک فایلsettings.jsonخالی یا متغیر محیطی که تعریف شده ولی مقدارش خالی است، همگی یک رشته خالی بهJSON.parseمیدهند. - خط جدید انتهایی در JSON خطبهخط.
text.split('\n').map(JSON.parse)روی رشته خالی بعد از آخرین خط جدید شکست میخورد. پیش از پارس، خطهای خالی را فیلتر کنید.
دو حالت شبیه به اینها رفتار متفاوتی دارند. کلیدی که در localStorage وجود ندارد null برمیگرداند و JSON.parse(null) بدون هیچ خطایی null برمیگرداند. متغیری که مقدار نگرفته undefined است و بهجای این خطا با "undefined" is not valid JSON شکست میخورد؛ این مورد در راهنمای خطای Unexpected token در JSON بررسی شده است.
برای اینکه ببینید با کدام حالت سروکار دارید، بدنه را پیش از پارس به شکل متن بخوانید و طولش را لاگ کنید:
const res = await fetch('/api/cart', { method: 'DELETE' });
const text = await res.text();
console.log(res.status, res.type, res.headers.get('content-type'), text.length);
// Typical results:
// 204 basic null 0 -> nothing to parse
// 0 opaque null 0 -> a no-cors request
// 200 basic application/json 8192 -> there is text: check whether it is complete
در مرورگر، پنل Network در ابزار توسعهدهنده همین را نشان میدهد: اگر زبانه Response خالی است، مشکل در درخواست یا سرور است، نه در کدی که پارس میکند.
وقتی متن نیمهکاره قطع شده است
اگر متن خالی نیست، یعنی پیش از کامل شدن سند تمام شده است. کروم و Node.js فعلی فقط وقتی پیام «Unexpected end of JSON input» را نشان میدهند که متن درست جایی قطع شود که باید یک مقدار شروع شود، یا وسط true، false یا null. در هر جای دیگر، همین قطع شدن پیام دیگری تولید میکند:
| انتهای متن | پیام در کروم و Node.js فعلی |
|---|---|
{"id": | Unexpected end of JSON input |
{"tags":["a", | Unexpected end of JSON input |
{"ok":tru | Unexpected end of JSON input |
{"name":"Al (۱۱ نویسه) | Unterminated string in JSON at position 11 |
{"id":42 (۸ نویسه) | Expected ',' or '}' after property value in JSON at position 8 |
قاعده کاربردی این است: اگر موقعیتی که در پیام آمده با طول متن برابر باشد، سند قطع شده است، نه اینکه ساختار نادرستی داشته باشد. نسخههای قدیمیتر کروم و Node.js بیشتر این حالتها را هم با «Unexpected end of JSON input» گزارش میکردند، برای همین در لاگها و پاسخهای قدیمیتر این عبارت برای هر نوع قطع شدن متن به کار رفته است.
JSON قطعشده معمولاً از یکی از این جاها میآید:
- پارس کردن جریان داده تکهبهتکه. دادهای که از
res.body.getReader()، رویدادdataدر Node.js یا یک سوکت میرسد، در تکههایی با اندازه دلخواه میآید و یک تکه تنها بهندرت سندی کامل است. تکهها را جمع کنید و یک بار در انتها پارس کنید، یا در JSON خطبهخط، داده را تا رسیدن هر خط جدید در بافر نگه دارید. - فضای ذخیرهسازی کوچکتر از داده. ستونی از پایگاه داده که از سند کوتاهتر است، در پایگاه دادهای که تنظیم شده بهجای رد کردن نوشتن، داده را کوتاه کند، بخش اول را ذخیره میکند و بقیه را دور میریزد.
- کپی کردن از جایی که متن طولانی را کوتاه میکند، مثل سامانه لاگی که برای طول هر خط سقف دارد.
- اتصالی که زودتر از موعد بسته شده، روی پاسخی که
Content-Lengthنداشته و به شکل chunked هم ارسال نشده است. وقتی طول اعلام شده باشد، قطع شدن اتصال بهجای این خطا به شکل خطای شبکه گزارش میشود: در Node.js،res.json()باTypeError: terminatedرد میشود.
پیشگیری از این خطا در کد
بدنه را به شکل متن بخوانید، تصمیم بگیرید بدنه خالی برای این endpoint چه معنایی دارد و فقط بعد از آن پارس کنید. این روش پاسخهای ۲۰۴، HEAD و ۲۰۰ خالی را در یک جا مدیریت میکند و وقتی مشکل دیگری پیش بیاید، پیام خطا را همچنان گویا نگه میدارد:
async function readJson(res) {
const text = await res.text();
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${text.slice(0, 100) || '(empty body)'}`);
}
if (text.trim() === '') return null; // 204, HEAD or an empty 200
try {
return JSON.parse(text);
} catch (err) {
throw new Error(`Invalid JSON (${text.length} chars): ${err.message}`);
}
}
برای مقدارهای ذخیرهشده، حالت خالی و حالت ناموجود را یکسان در نظر بگیرید و به یک مقدار پیشفرض برگردید:
function loadCart() {
const raw = localStorage.getItem('cart');
if (!raw) return []; // null (missing) or '' (empty)
try {
return JSON.parse(raw);
} catch {
return []; // damaged value: start again
}
}
در سمت سرور هم یکپارچه عمل کنید: یا ۲۰۴ را بدون بدنه و بدون نوع محتوای JSON برگردانید، یا یک سند JSON کوچک مثل {} یا {"deleted": true}. پاسخ ۲۰۰ با برچسب application/json و بدنه خالی، دقیقاً همان ترکیبی است که کلاینتها را از کار میاندازد.
بررسی دستی متن
وقتی بدنه خالی نیست، آن را از پنل Network یا لاگهایتان کپی کنید و در اعتبارسنج JSON بچسبانید. این ابزار از پارسر خود مرورگر استفاده میکند، پس همان پیام و موقعیتی را نشان میدهد که کد شما گرفته است و وقتی سند معتبر باشد، آن را فرمت میکند. اگر موقعیت گزارششده درست در انتهای متنی است که چسباندهاید، بخش گمشده را در مسیر انتقال یا ذخیرهسازی جستجو کنید، نه در خود داده. برای خطاهایی که نویسه مشخصی را نام میبرند، مثل < از یک صفحه HTML، راهنمای خطای Unexpected token در JSON را ببینید.