خطأ Unexpected Token in JSON: معناه وطريقة حله
هذا الخطأ يعني أن النص الذي حاولت تحليله ليس JSON أصلًا، والرمز الذي تذكره الرسالة يكشف في الغالب ما الذي وصلك بدلًا منه.
تستدعي JSON.parse() أو response.json() فيصلك SyntaxError بدل البيانات التي تنتظرها. تختلف صياغة الرسالة من متصفح إلى آخر، لكن معناها واحد في كل الحالات: قرأ المحلّل النص من بدايته حتى اصطدم بمحرف لا يجوز أن يظهر في هذا الموضع من مستند JSON صالح. والمحرف الذي تقتبسه الرسالة، ومعه رقم الموضع، هما أنفع دليلين بين يديك لمعرفة ما حدث فعلًا، لذا لا تتجاوزهما وأنت تقرأ الخطأ.
الخطأ نفسه في محركات مختلفة
غيّرت الإصدارات الأحدث من Chrome وNode.js صياغة هذه الرسالة، ولهذا ستجد الصيغتين معًا في نتائج البحث وفي السجلات القديمة. الجدول التالي يجمع الصيغ التي ستصادفها بحسب المكان الذي يعمل فيه الكود:
| أين تراه | الرسالة |
|---|---|
| Chrome وEdge وNode.js 20 وما بعده | Unexpected token '<', "<!DOCTYPE "... is not valid JSON |
| إصدارات Chrome الأقدم وNode.js 18 | Unexpected token < in JSON at position 0 |
| Firefox | JSON.parse: unexpected character at line 1 column 1 of the JSON data |
| Safari | JSON Parse error: Unrecognized token '<' |
عبارة "position 0" أو "line 1 column 1" تعني أن المحرف الأول نفسه كان خاطئًا منذ البداية. وهذا يشير في أغلب الأحوال إلى أن الاستجابة شيء آخر غير JSON تمامًا، لا إلى خطأ مطبعي صغير داخل مستند JSON سليم في معظمه.
السبب المعتاد: صفحة HTML بدل JSON
المحرف < غير المتوقع في الموضع 0 هو أشهر صور هذا الخطأ بفارق كبير. طلب الكود JSON من واجهة API، لكن الخادم ردّ بصفحة HTML، وصفحات HTML تبدأ بـ <!DOCTYPE html> أو <html>، فيتوقف المحلّل عند أول محرف. ومن الأسباب المعتادة لوصول صفحة HTML بدل البيانات:
- رابط خاطئ: خطأ مطبعي أو بادئة
/apiناقصة، فيعيد الخادم صفحة 404 الخاصة بالموقع. - خطأ في الخادم: يعرض إطار العمل صفحة خطأ 500 مكتوبة بـ HTML.
- جلسة منتهية: يُعاد توجيه الطلب إلى صفحة تسجيل الدخول، و
fetchيتبع إعادة التوجيه بصمت دون أن ينبّهك. - المسار الاحتياطي لتطبيقات الصفحة الواحدة: يعيد خادم التطوير أو الاستضافة الملف
index.htmlلأي مسار غير معروف، وبرمز الحالة 200 أيضًا. - وسيط أو CDN أو جدار حماية في منتصف الطريق يرسل صفحة خطأ أو صفحة تحقّق خاصة به بصيغة HTML.
أسرع طريقة للتأكد من ذلك هي النظر إلى الاستجابة الخام قبل محاولة تحليلها:
const res = await fetch('/api/items');
console.log(res.status, res.headers.get('content-type'));
const text = await res.text();
console.log(text.slice(0, 200)); // shows the HTML if that is what came back
ويمكنك في المتصفح أيضًا فتح لوحة Network في أدوات المطوّر، والنقر على الطلب، ثم مراجعة لسان Response. إن رأيت HTML هناك فالحل في إصلاح الرابط أو المصادقة أو خطأ الخادم؛ أما كود التحليل نفسه فسليم ولا يحتاج إلى تعديل.
رموز أخرى وما تدل عليه
ليست صفحات HTML الاحتمال الوحيد. كل رسالة من الرسائل التالية تشير إلى نوع مختلف من المدخلات، ومعرفة النوع تختصر عليك نصف الطريق إلى الحل:
| الرسالة الحالية (الرسالة الأقدم) | ما الذي حلّلته فعلًا | الحل |
|---|---|---|
"undefined" is not valid JSON (Unexpected token u in JSON at position 0) | القيمة undefined، مثل مفتاح غير موجود في localStorage أو متغير لم تُسند إليه قيمة | تأكّد من وجود القيمة قبل تحليلها |
"[object Object]" is not valid JSON (Unexpected token o in JSON at position 1) | كائن حُلِّل من قبل، فحوّله JSON.parse إلى نص | استخدم الكائن مباشرة ولا تحلّله مرتين |
Expected property name or '}' in JSON at position 1 | علامات اقتباس مفردة أو مفاتيح بلا علامات اقتباس، كما في {'a': 1} أو {a: 1} | ضع علامات اقتباس مزدوجة حول كل مفتاح وكل نص |
Expected double-quoted property name in JSON at position 8 | فاصلة زائدة في النهاية، كما في {"a": 1,} | احذف الفاصلة التي تسبق قوس الإغلاق |
Unexpected token 'N', "{"a": NaN}" is not valid JSON | القيم NaN أو Infinity أو undefined داخل البيانات | استبدلها بـ null أو برقم |
| رسالة تقتبس رمزًا غير مرئي أو يبدو فارغًا في الموضع 0 | علامة ترتيب البايتات (BOM) في بداية ملف حفظه بعض محرّرات Windows | احفظ الملف بترميز UTF-8 دون BOM، أو احذف المحرف الأول |
تحليل بيانات محلَّلة أصلًا
كثيرًا ما تسلّمك المكتبات كائنات جاهزة دون أن تنتبه. فالدالة response.json() تعيد كائن JavaScript مباشرة، وكذلك تفعل axios في response.data. وتمرير هذا الكائن إلى JSON.parse يحوّله أولًا إلى النص "[object Object]"، فيفشل التحليل عند الموضع 1. الحل هنا بسيط: احذف خطوة التحليل الزائدة.
const data = await res.json();
// JSON.parse(data) <- fails: data is already an object
console.log(data.items);
العثور على خطأ داخل JSON حقيقي
حين يقع موضع الخطأ في مكان ما وسط النص، فالنص JSON فعلًا لكن فيه خطأ. الصقه في مدقّق JSON: يستخدم المحلّل نفسه الذي يستخدمه متصفحك، ويعرض الرسالة مع الموضع ورقم السطر، وينسّق المستند فور أن يصبح صالحًا. والخطأ الفعلي يقع عادةً قبل الموضع المذكور مباشرة، كفاصلة ناقصة بين عنصرين، أو علامة اقتباس كان يجب أن تكون مزدوجة. لذلك ابدأ البحث من الموضع المذكور وارجع إلى الوراء محرفًا محرفًا.
الحماية من الخطأ في الكود
فحص رمز الحالة ونوع المحتوى قبل التحليل يحوّل خطأ صياغة محيّرًا إلى رسالة تقول بوضوح ما الذي حدث فعلًا:
async function getJson(url) {
const res = await fetch(url);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.includes('application/json')) {
const body = await res.text();
throw new Error(`Expected JSON from ${url}, got ${res.status} ${type}: ${body.slice(0, 100)}`);
}
return res.json();
}
والفكرة نفسها تنطبق على جهة الخادم: أعِد أجسام الأخطاء بصيغة JSON من مسارات الـ API بدل صفحات الخطأ المكتوبة بـ HTML، وأرسل معها الترويسة Content-Type: application/json. وإن كانت الرسالة التي تواجهها هي القريبة جدًا من هذا الخطأ، أي التي تظهر حين يكون جسم الاستجابة فارغًا أو مبتورًا، فراجع دليل Unexpected end of JSON input.