خطأ 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 18Unexpected token < in JSON at position 0
FirefoxJSON.parse: unexpected character at line 1 column 1 of the JSON data
SafariJSON 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.

الأسئلة الشائعة

ماذا تعني رسالة "Unexpected token < in JSON at position 0"؟

بدأت الاستجابة بالمحرف <، أي أنها HTML وليست JSON، وغالبًا ما تكون صفحة خطأ أو صفحة تسجيل دخول أو ملف index.html احتياطيًا. افحص رابط الطلب ورمز الحالة وترويسة Content-Type.

لماذا تقول الرسالة إن "[object Object]" ليس JSON صالحًا؟

لأنك مرّرت كائنًا إلى JSON.parse، فتحوّل أولًا إلى النص [object Object]. القيمة محلَّلة أصلًا، فاستخدمها مباشرة دون تحليل.

هل يستطيع مدقّق JSON العثور على المشكلة؟

نعم، إن كان النص يُفترض أن يكون JSON، إذ يعرض المدقّق الموضع الدقيق لأول خطأ. أما إن كان النص HTML فالحل في الخادم أو في الطلب، لا في JSON.