تخطَّ إلى المحتوى

١١ يوليو ٢٠٢٦ · آخر تحديث: ١٩ أكتوبر ٢٠٢٦

حل خطأ Unexpected token in JSON بالعربي

شارك المقال:

من أكثر أخطاء جافاسكريبت شيوعًا عند التعامل مع أي API:

SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON

أو بصيغته القديمة التي ما زالت تظهر في إصدارات قديمة من Chrome وNode:

SyntaxError: Unexpected token < in JSON at position 0

الاسم يوحي بأن مشكلتك في JSON، والحقيقة في أغلب الحالات: ما وصلك ليس JSON أصلًا، وأول حرف منه فضح ذلك.

اقرأ البصمة: الحرف يخبرك بالسبب

كل رسالة في هذا الجدول نسخناها من تشغيل فعلي في Node.js 22 وChromium:

الرسالةما وصلك فعليًاالسبب الغالب
Unexpected token '<', "<!DOCTYPE "...صفحة HTMLخطأ 404 أو 500، صفحة تسجيل دخول، رابط خاطئ
"undefined" is not valid JSONundefined أو النص "undefined"قيمة غير موجودة، غالبًا من localStorage
"[object Object]" is not valid JSONكائن جاهز لا نصتحليل بيانات محلَّلة مسبقًا
Unexpected token 'I', "Internal S"...رسالة خطأ نصيةInternal Server Error بلا JSON
Unexpected token 'P', "Proxy erro"...رسالة proxyخادم التطوير لم يصل للـ API
Unexpected end of JSON inputنص فارغرد بلا جسم، مثل 204 No Content
Expected property name or '}' in JSON at position 1JSON مكسورمفاتيح بلا " أو اقتباسات مفردة '
Expected double-quoted property name ... position 8JSON مكسورفاصلة زائدة قبل }
Unexpected non-whitespace character after JSONقيمتان متتاليتانكائنان ملصوقان، أو نص زائد بعد الـ JSON

وفي المتصفحات الأخرى ترى المعنى نفسه بصياغة أخرى: 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 حقيقيًا مكسورًا في ذلك المكان بالضبط.

الخطوة الذهبية: اطبع النص الخام

قبل أي إصلاح، بدّل التحليل بالطباعة:

// ما كنت تفعله
const data = await res.json();

// التشخيص: ماذا وصل فعلًا؟
const raw = await res.text();
console.log(res.status, res.headers.get("content-type"));
console.log(raw.slice(0, 200));   // أول 200 حرف تكشف كل شيء

سترى فورًا <!DOCTYPE html>... أو نصًا فارغًا أو JSON فيه كسر واضح. وتبويب Network في أدوات المطوّر (F12) يعرض الرد نفسه بنقرة: الحالة، والـ Content-Type، والجسم.

انتبه: الجسم يُقرأ مرة واحدة. إن استدعيت res.json() بعد res.text() فلن ترى خطأ JSON أصلًا، بل خطأ مختلفًا. جرّبناه: Chromium يكتب TypeError: Failed to execute 'json' on 'Response': body stream already read، وNode.js يكتب TypeError: Body is unusable: Body has already been read. اقرأ بـ text() ثم حلّل النص بنفسك بـ JSON.parse(raw).

الحلول حسب الحرف

1) '<': وصلتك صفحة HTML، فالمشكلة في الطلب لا في التحليل

الخادم أعاد صفحة خطأ أو صفحة تسجيل دخول. افحص الحالة قبل التحليل دائمًا:

const res = await fetch("/api/lessons");
if (!res.ok) {
  // 404؟ 500؟ الرسالة الحقيقية هنا، فلا تتركها تنفجر كخطأ JSON مضلّل
  throw new Error(`API error ${res.status}: ${(await res.text()).slice(0, 200)}`);
}
const data = await res.json();

ثم صحّح السبب:

  • الرابط: خطأ إملائي، أو مسار نسبي (api/lessons بلا / في البداية) يُحل بالنسبة للصفحة الحالية، فيصبح /blog/api/lessons في صفحة فرعية ويعود 404.
  • في تطبيق React على خادم تطوير Vite: مسار غير موجود يعيد صفحة التطبيق نفسها (index.html) بالحالة 200. جرّبنا fetch("/api/lessons") على Vite 8 بلا خادم API خلفه، فعاد 200 text/html. لهذا res.ok صحيح والرد HTML. افحص Content-Type: إن لم يكن application/json فأنت تطلب المسار الخطأ، أو نسيت إعداد server.proxy الذي يوجّه /api للخادم الخلفي.
  • المصادقة: جلسة منتهية تحوّلك لصفحة الدخول.
  • الخادم: حالة 500 تعني عطلًا عنده: حل خطأ 500. وإن كان الطلب محجوبًا بين نطاقين فالسبب CORS.

2) "undefined": قيمة غير موجودة

localStorage.setItem("user", undefined);    // يخزّن النص "undefined"
JSON.parse(localStorage.getItem("user"));   // "undefined" is not valid JSON

localStorage يحفظ النصوص فقط، فيحوّل undefined إلى الكلمة "undefined". احفظ دائمًا بـ JSON.stringify واقرأ بأمان:

function readJSON(key, fallback = null) {
  const raw = localStorage.getItem(key);       // null إن لم يوجد المفتاح
  if (raw === null || raw === "undefined") return fallback;
  try {
    return JSON.parse(raw);
  } catch {
    return fallback;                           // بيانات قديمة تالفة
  }
}

ومن المفيد أن تعرف أن JSON.parse(null) لا يفشل، بل يعيد null، لأن null تتحوّل للنص "null" وهو JSON صالح. لذلك يظهر الخطأ مع undefined لا مع null. (والفرق بين القيمتين بالتفصيل في الفرق بين undefined وnull.)

3) "[object Object]": تحلّل ما هو محلَّل أصلًا

const res = await fetch("/api/user");
const user = await res.json();     // كائن جاهز
JSON.parse(user);                  // "[object Object]" is not valid JSON

res.json() وaxios يعيدان كائنًا جاهزًا، فلا تمرّره لـ JSON.parse مرة ثانية. القاعدة: JSON.parse يأخذ نصًا فقط.

4) 'I' أو 'P' أو أي حرف كبير: رسالة خطأ نصية

Internal Server Error أو Proxy error: Could not proxy request أو Too Many Requests: الخادم أو الـ proxy ردّ برسالة عادية لا JSON. الحرف الأول من الرسالة هو ما يظهر لك. الحل نفسه: res.ok أولًا، ثم اقرأ النص لتعرف الرسالة كاملة.

5) فراغ: Unexpected end of JSON input

الرد بلا جسم: 204 No Content، أو DELETE ناجح لا يعيد شيئًا، أو خادم انهار في منتصف الرد. افحص قبل التحليل:

if (res.status === 204) return null;
const text = await res.text();
const data = text ? JSON.parse(text) : null;

6) JSON مكسور فعلًا: قواعد أصرم من جافاسكريبت

// صالح في جافاسكريبت، مكسور في JSON
{
  name: 'أحمد',          // المفاتيح والنصوص: علامة " المزدوجة فقط
  age: 17,               // الفاصلة الأخيرة ممنوعة
  // والتعليقات ممنوعة أيضًا
}

// JSON سليم
{ "name": "أحمد", "age": 17 }

الموضع في الرسالة يدلك على المكان: at position 8 (line 1 column 9) يعني الحرف التاسع في السطر الأول. وفي ملفات الإعداد (package.json مثلًا) يشير المحرر للسطر المكسور فورًا.

حرف خفي آخر: BOM، علامة غير مرئية (U+FEFF) يضعها Notepad القديم وبعض المحررات في أول الملف. الرسالة تبدو غريبة: Unexpected token '' وبين علامتي الاقتباس حرف موجود لكنك لا تراه. احفظ الملف بترميز «UTF-8» بلا BOM.

وعلى الخادم: أنشئ الرد بأداة التسلسل الرسمية (JSON.stringify أو res.json() في Express، وjson.dumps أو JsonResponse في بايثون)، لا بلصق النصوص يدويًا.

نفس الخطأ في بايثون

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

جرّبنا: json.loads("") وjson.loads("<!DOCTYPE html>") يعطيان هذه الرسالة نفسها بالضبط. مع requests تظهر كـ requests.exceptions.JSONDecodeError عند response.json(). التشخيص نفسه:

import requests

r = requests.get("https://example.com/api/lessons", timeout=10)
print(r.status_code, r.headers.get("Content-Type"))
print(r.text[:200])
r.raise_for_status()      # يرمي خطأ HTTP واضحًا بدل خطأ JSON مضلّل
data = r.json()

وأخطاء الكسر الحقيقي لها رسائل أوضح في بايثون: Expecting property name enclosed in double quotes للاقتباس المفرد، و Illegal trailing comma before end of object للفاصلة الزائدة.

قائمة تحقّق سريعة

  • اطبع res.status وContent-Type وأول 200 حرف من res.text().
  • '<'؟ صفحة HTML: الرابط، أو المصادقة، أو 404 مقنّع بـ 200.
  • "undefined"؟ قيمة غير موجودة، غالبًا localStorage.
  • "[object Object]"؟ لا تحلّل كائنًا محلَّلًا.
  • end of JSON input؟ جسم فارغ أو 204.
  • موضع متقدم؟ JSON مكسور: اقتباسات مزدوجة، ولا فواصل أخيرة، ولا تعليقات.
  • res.ok قبل res.json() في كل fetch تكتبه.

الخلاصة

Unexpected token in JSON رسالة مضلّلة الاسم: نادرًا ما تكون مشكلة JSON، وغالبًا رد غير متوقع من الخادم التقطه JSON.parse أولًا. اقرأ الحرف، واطبع النص الخام، وافحص الحالة، وستجد السبب الحقيقي خلال دقيقة.

التعامل مع البيانات من الأساس في درس JSON وfetch، وأخطاء العائلة في أشهر أنواع TypeError وحل Cannot read properties of undefined.

📚 مصادر رسمية للتعمّق: توثيق JavaScript على MDN

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

لماذا يقول position 0 دائمًا تقريبًا؟

لأن أول حرف في النص هو أول ما يفحصه المحلل. إن كان الرد صفحة HTML فأول حرف فيها < من <!DOCTYPE، ولا يبدأ أي JSON صالح بهذا الحرف، فيفشل التحليل عند الموضع صفر فورًا. الموضع صفر يعني غالبًا أن ما وصلك ليس JSON أصلًا، والموضع المتقدم يعني JSON مكسورًا في منتصفه.

ما معنى Unexpected token u in JSON at position 0؟

حرف u هو أول حرف من undefined: مرّرت لـ JSON.parse قيمة غير موجودة، أو النص "undefined" نفسه. أشهر مصدر: localStorage.setItem("user", undefined) يخزّن الكلمة "undefined" كنص، ثم يفشل JSON.parse عند قراءتها. في المتصفحات الحديثة تظهر الرسالة بصيغة "undefined" is not valid JSON.

الـ API يعمل في Postman ويفشل في موقعي، لماذا؟

الرد الذي يصل موقعك مختلف: جلسة منتهية تعيد صفحة تسجيل دخول HTML، أو مسار نسبي خاطئ يصيب صفحة 404، أو proxy خادم التطوير يعيد Proxy error نصًا. افتح تبويب Network في أدوات المطوّر: الرد الحقيقي مكتوب هناك حرفيًا.

نفس الخطأ في بايثون؟

في بايثون يظهر بصيغة json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)، أو requests.exceptions.JSONDecodeError عند response.json(). المعنى نفسه: النص فارغ أو ليس JSON. اطبع response.status_code وresponse.text[:200] قبل التحليل.

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.