خطاهای JSON: چطور JSON نامعتبر را پیدا و درست کنیم
ویرگول اضافه در پایان، گیومهی تکی، توضیح (comment)، یک BOM نامرئی: ۲۶ تکه JSON خراب را به Chrome و Python و PHP دادیم و یادداشت کردیم هر کدام چه میگوید. اینجا میبینید پیام خطا را چطور بخوانید و علت اصلی را چطور برطرف کنید.
JSON قالبی سختگیر است: یک ویرگول در جای اشتباه کافی است تا کل فایل رد شود. پیامهای خطا هم کمک چندانی نمیکنند. Chrome یک چیز میگوید، Python چیز دیگری، و PHP اغلب فقط مینویسد «Syntax error». برای اینکه بفهمیم هر پیام واقعاً چه معنایی دارد، ۲۶ تکهی کوتاه با رایجترین اشتباهها نوشتیم و آنها را با Chrome 141، Node.js 22 (هر دو از موتور V8 استفاده میکنند، همینطور Microsoft Edge)، Python 3.13 و PHP 8.3 خواندیم. JSON خودتان را میتوانید در ابزار مرتبسازی JSON ما بچسبانید: خط و ستون نخستین خطا را نشان میدهد و علت احتمالی را به فارسی میگوید.
JSON معتبر چه شکلی است
JSON در دو سند تعریف شده که با هم یکیاند: RFC 8259 از IETF و ECMA-404. قواعدش در یک صفحه جا میشود:
- یک متن JSON دقیقاً یک مقدار است: یک شیء
{…}، یک آرایه[…]، یک رشتهی متنی، یک عدد،true،falseیاnull. - رشتهها و نام ویژگیها داخل گیومهی دوتایی صاف
"میآیند. گیومهی تکی و گیومههای فارسی و خمیده (« » و “ ”) مجاز نیستند. - موردها با ویرگول انگلیسی از هم جدا میشوند، اما بعد از آخرین مورد ویرگول نمیآید.
- توضیح (comment) وجود ندارد.
- عددها دهدهیاند، با رقمهای لاتین و نقطهی اعشار: بدون صفر در ابتدا (
007)، بدون رقم فارسی، بدون مبنای شانزده (0xFF)، بدونNaNوInfinity. - داخل رشتهها باید گیومهی دوتایی، بکاسلش و نویسههای کنترلی مثل شکست خط و Tab را escape کرد:
\"،\\،\n،\t. - فاصله، Tab و شکست خط میان موردها همهجا مجاز است.
خیلی چیزها شبیه JSONاند ولی JSON نیستند: یک شیء در کد JavaScript، یک دیکشنری Python که با print() چاپ شده، یک فایل تنظیمات با توضیح. آنقدر شبیهاند که چشم را فریب دهند و آنقدر متفاوت که در هر parser رد شوند.
یک اشتباه، سه پیام متفاوت
اینها پیامهای سه parser برای رایجترین اشتباههاست. Chrome و Node.js در همهی موارد پیام یکسان دادند.
| اشتباه | Chrome / Node.js | Python 3.13 | PHP 8.3 |
|---|---|---|---|
ویرگول پیش از }: {"age": 31,} | Expected double-quoted property name in JSON at position 11 (line 1 column 12) | Illegal trailing comma before end of object: line 1 column 11 (char 10) | Syntax error |
ویرگول پیش از ]: [1, 2, 3,] | Unexpected token ']', "[1, 2, 3,]" is not valid JSON | Illegal trailing comma before end of array | Syntax error |
گیومهی تکی: {'name': 'Anna'} | Expected property name or '}' in JSON at position 1 | Expecting property name enclosed in double quotes | Syntax error |
| جا افتادن ویرگول میان دو ویژگی | Expected ',' or '}' after property value in JSON | Expecting ',' delimiter | Syntax error |
یک // توضیح در فایل | Expected property name or '}' in JSON | Expecting property name enclosed in double quotes | Syntax error |
NaN بهعنوان مقدار | Unexpected token 'N', … is not valid JSON | بدون خطا پذیرفته شد | Syntax error |
| شکست خط داخل رشته | Bad control character in string literal in JSON | Invalid control character at | Control character error, possibly incorrectly encoded |
مسیر ویندوز "C:\Users\anna" | Bad escaped character in JSON | Invalid \escape | Syntax error |
| دو شیء پشت سر هم | Unexpected non-whitespace character after JSON | Extra data | Syntax error |
| ورودی خالی | Unexpected end of JSON input | Expecting value: line 1 column 1 (char 0) | Syntax error |
| یک صفحهی HTML بهجای JSON | Unexpected token '<', "<!DOCTYPE "... is not valid JSON | Expecting value: line 1 column 1 (char 0) | Syntax error |
دو نکته به چشم میآید. Python از همه کمککنندهتر است: ویرگول اضافه و نشانهی BOM را صریحاً نام میبرد. PHP تقریباً همیشه فقط «Syntax error» میگوید و جای خطا را باید خودتان پیدا کنید. و Python مقدار NaN را پذیرفت، در حالی که JSON معتبر نیست. پایینتر به این برمیگردیم.
پیام خطا را چطور بخوانیم
وقتی پیامی موقعیت (position) دارد، نویسهها را از صفر میشمارد: «position 11» یعنی نویسهی دوازدهم، و برای همین Chrome اضافه میکند «line 1 column 12». Python هم خط و ستون را از یک میشمارد، اما خودِ ویرگول را نشان میدهد (ستون ۱۱). موقعیت جایی است که parser دست کشیده، و این جا اغلب کمی بعد از اشتباه واقعی است:
- ویرگول پیش از بستن آکولاد را Chrome روی خودِ آکولاد گزارش میکند، چون parser تازه وقتی که منتظر ویژگی بعدی است و بهجایش
}میبیند متوجه مشکل میشود. - ویرگول جاافتاده در ابتدای مورد بعدی گزارش میشود، نه در پایان مورد قبلی.
- آکولاد یا کروشهی بستهی جاافتاده در انتهای متن گزارش میشود، هر قدر هم که بالاتر جا افتاده باشد.
یک نکته دربارهی متنهایی که شکلک (emoji) دارند: JavaScript هر شکلک را دو نویسه میشمارد، پس بعد از یک شکلک شمارهی ستون در Chrome یکی بیشتر است. Python و ابزار ما آن را یک نویسه حساب میکنند.
نسخههای قدیمیتر مرورگرها همین خطاها را با عبارت دیگری مینوشتند. معروفترینش «Unexpected token < in JSON at position 0» است که همان معنای سطر HTML در جدول را دارد: متن با < شروع میشود.
رایجترین علتها و راه رفعشان
ویرگول بعد از آخرین مورد
JavaScript اجازه میدهد بعد از آخرین مورد یک شیء یا آرایه ویرگول بیاید؛ JSON نه. این ویرگول معمولاً وقتی پیدا میشود که خطها را دستی حذف یا جابهجا میکنیم. ویرگول پیش از } یا ] را پاک کنید.
گیومهی تکی، فارسی و خمیده
JSON فقط گیومهی دوتایی صاف " را میپذیرد. گیومهی فارسی « » یا گیومههای خمیده “ ” وقتی وارد میشوند که JSON از Word، یک پیامرسان یا برنامهی ایمیل کپی شود، یا وقتی صفحهکلید روی فارسی مانده باشد. چنین متنی را اول در یک ویرایشگر متن ساده بچسبانید یا گیومهها را همانجا عوض کنید.
شیء JavaScript و دیکشنری Python
نام ویژگی بدون گیومه ({name: "Anna"}) در JavaScript درست است اما JSON نیست. در Python، دستور print(data) یا str(data) گیومهی تکی و True و False و None میسازد که همه نامعتبرند. در JavaScript از JSON.stringify() و در Python از json.dumps() استفاده کنید که همیشه JSON واقعی مینویسند.
توضیح (comment)
JSON استاندارد توضیح ندارد. بعضی ابزارها قالبهای گسترشیافتهای به کار میبرند که توضیح را میپذیرند، مثل «JSON with Comments» در تنظیمات Visual Studio Code یا فایل tsconfig.json در پروژههای TypeScript. برای همین یک فایل در یک برنامه کار میکند و در برنامهی دیگر نه. پیش از فرستادن داده به یک parser استاندارد، // … و /* … */ را حذف کنید.
NaN و Infinity و undefined
JSON راهی برای نوشتن «عدد نیست» یا «بینهایت» ندارد. JavaScript این را میداند: JSON.stringify() بهجایش null مینویسد. Python نه: json.dumps(float('nan')) مقدار NaN را مینویسد که خود Python بیاعتراض دوباره میخواند اما همهی مرورگرها ردش میکنند. با allow_nan=False در json.dumps() بهجای JSON نامعتبر خطا میگیرید. undefined اصلاً مقدار JSON نیست؛ JSON.stringify() چنین ویژگیهایی را حذف میکند.
رقم فارسی، صفر در ابتدا و ممیز
عدد در JSON فقط با رقمهای لاتین 0 تا 9 نوشته میشود. در آزمایش ما {"a": ۱۲} با رقم فارسی هم در Chrome و هم در Python رد شد. داخل رشتهها رقم فارسی هیچ اشکالی ندارد: "price": "۱۲۰٬۰۰۰" معتبر است، اما آنوقت مقدار یک متن است نه عدد.
شمارهی موبایل مثل 09121234567 یا کد پستی با صفر در ابتدا، در معنای JSON عدد نیست، چون عدد نمیتواند با صفر شروع شود. آن را بهشکل متن بنویسید: "mobile": "09121234567". اعشار را هم فقط با نقطه بنویسید: 0.5، نه ممیز فارسی (٫) و نه ویرگول.
شکست خط و بکاسلش داخل رشته
شکست خط یا Tab واقعی داخل رشته مجاز نیست؛ بهجایش \n و \t بنویسید. بکاسلش شروع یک دنبالهی escape است، پس در مسیر ویندوز باید دوتایی نوشته شود: "C:\\Users\\anna". یا از اسلش معمولی استفاده کنید که ویندوز در بیشتر جاها آن را هم میفهمد.
HTML بهجای JSON
اگر خطا به نخستین نویسه اشاره کند و از < نام ببرد، سرور اصلاً JSON نفرستاده است، بلکه معمولاً یک صفحهی HTML: صفحهی خطای 404 یا 500، صفحهی ورود یا پیام تعمیرات. پیش از گشتن دنبال خطای نگارشی، کد وضعیت و سرآیند Content-Type پاسخ را بررسی کنید.
نویسههای نامرئی
برخی ویرایشگرها و خروجیگیرها فایل را بهصورت «UTF-8 with BOM» ذخیره میکنند: با یک نشانهی نامرئی (U+FEFF) در ابتدای فایل. JSON.parse() آن را رد میکند و پیام Python حتی راه حل را هم میگوید: فایل را با utf-8-sig بخوانید. طبق RFC 8259 برنامهها نباید به JSONی که میفرستند BOM اضافه کنند. ابزار ما آن را حذف میکند و خبر میدهد.
برای کسی که فارسی تایپ میکند، نویسههای نامرئی دیگری هم مهماند. در آزمایش ما نیمفاصله (U+200C) و نشانهی جهت راستبهچپ (U+200F) بیرون از رشتهها، و همینطور فاصلهی نشکن (U+00A0) که هنگام کپی از صفحههای وب همراه متن میآید، هم در Chrome و هم در Python باعث خطا شدند. داخل رشتهها همهشان مجازند؛ مثلاً "name": "علیرضا" با نیمفاصله معتبر است. اگر خطا به جایی اشاره میکند که چیز اشتباهی نمیبینید، فاصلهی آنجا را پاک کنید و دوباره تایپش کنید.
بیش از یک مقدار
دو شیء پشت سر هم یک متن JSON نیستند. فایلهای گزارش (log) و خروجی دادهها اغلب از JSON Lines (یا NDJSON) استفاده میکنند که هر خط یک شیء است. این فایلها درستاند، اما هر خط را باید جداگانه خواند.
معتبر، ولی غافلگیرکننده
بعضی JSONها معتبرند اما باز هم آن کاری را که انتظار دارید نمیکنند:
| ورودی | Chrome / Node.js | Python 3.13 | PHP 8.3 |
|---|---|---|---|
{"a": 1, "a": 2} | مقدار a میشود ۲ | مقدار a میشود ۲ | مقدار a میشود ۲ |
{"id": 12345678901234567890} | 12345678901234567000 | 12345678901234567890 (دقیق) | 1.2345678901234567E+19 (عدد اعشاری) |
سطر دوم در عمل مهم است. JavaScript و PHP عددها را بهصورت عدد اعشاری ۶۴ بیتی نگه میدارند که فقط تا ۹٬۰۰۷٬۱۹۹٬۲۵۴٬۷۴۰٬۹۹۱ (یعنی ۲ به توان ۵۳ منهای ۱) دقیق است. RFC 8259 توصیه میکند در همین بازه بمانید. پس شناسههای بزرگ، مثلاً از پایگاهدادهها یا شبکههای اجتماعی، را بهتر است بهشکل متن بفرستید.
یک نکتهی دیگر برای متن فارسی: json.dumps() در Python بهطور پیشفرض هر حرف غیرلاتین را بهشکل \u مینویسد. نتیجه معتبر است اما خواندنش برای انسان سخت است. با json.dumps(data, ensure_ascii=False) متن فارسی همانطور که هست ذخیره میشود. در جهت دیگر، JSON.stringify() ویژگیهایی را که مقدارشان undefined یا تابع است بیصدا حذف میکند، NaN و Infinity را به null و تاریخها را به متن تبدیل میکند، و برای BigInt حتی خطا میدهد («Do not know how to serialize a BigInt»).
فهرست بررسی وقتی JSON رد میشود
- بررسی کنید واقعاً JSON دریافت کردهاید: کد وضعیت 200 و
Content-Typeبرابرapplication/json. - متن را در یک ابزار مرتبسازی بچسبانید و به خط گزارششده و نویسهی پیش از آن نگاه کنید.
- سراغ مظنونان همیشگی بروید: ویرگول پایانی، گیومهی تکی یا فارسی، توضیح، نام بدون گیومه.
- مقدارها را بررسی کنید:
True/Noneاز Python،NaN، رقم فارسی، عدد با صفر در ابتدا یا ممیز. - رشتهها را بررسی کنید: شکست خط واقعی، بکاسلش تکی در مسیرها، نیمفاصله و فاصلهی نشکن بیرون از گیومه.
- برنامهای را که JSON را میسازد اصلاح کنید تا خطا دوباره پیش نیاید: بهجای سرهم کردن دستی JSON، از
JSON.stringify()،json.dumps()یاjson_encode()استفاده کنید.
ابزارهای این راهنما
پرسشهای متداول
چرا JSON اجازه نمیدهد بعد از آخرین مورد ویرگول بیاید؟
دستور زبان JSON در RFC 8259 و ECMA-404 چنین چیزی را پیشبینی نکرده و این قالب عمداً تغییر داده نمیشود تا همهی parserها دادهی یکسان را یکسان بخوانند. قالبهای گسترشیافتهای مثل JSON5 ویرگول پایانی و توضیح را میپذیرند، اما parser استاندارد JSON آنها را رد میکند.
میتوانم در JSON توضیح (comment) بنویسم؟
در JSON استاندارد نه. بعضی برنامهها در فایلهای تنظیمات خودشان توضیح را میپذیرند، مثل تنظیمات Visual Studio Code یا tsconfig.json در TypeScript، اما دادهای که به یک API میفرستید یا با JSON.parse() میخوانید نباید توضیح داشته باشد. اگر یادداشت لازم دارید، آن را بهشکل یک ویژگی معمولی مثل "_comment" اضافه کنید.
پیام «Unexpected token < in JSON at position 0» یعنی چه؟
متن با «<» شروع میشود، پس تقریباً بهطور قطع HTML است، مثلاً یک صفحهی خطا یا صفحهی ورود، و JSON نیست. نسخههای جدیدتر Chrome آن را بهشکل Unexpected token '<', "<!DOCTYPE "... is not valid JSON مینویسند. آدرسی را که فراخواندهاید و کد وضعیت پاسخ را بررسی کنید.
میتوانم در JSON عدد را با رقم فارسی بنویسم؟
نه. عدد در JSON فقط با رقمهای 0 تا 9 نوشته میشود و رقم فارسی خطا میدهد. اگر میخواهید عدد با رقم فارسی نمایش داده شود، آن را با رقم لاتین ذخیره کنید و هنگام نمایش به فارسی تبدیل کنید، یا کل مقدار را بهشکل متن بین گیومه بنویسید.
آیا JSONی که در ابزار شما میچسبانم جایی بارگذاری میشود؟
نه. ابزار مرتبسازی JSON ما کاملاً در مرورگر شما اجرا میشود. متن روی دستگاه خودتان بررسی و مرتب میشود و به هیچ سروری فرستاده نمیشود.
چرا شناسهی عددی بزرگم بعد از خواندن عوض میشود؟
JavaScript همهی عددها را بهصورت عدد اعشاری ۶۴ بیتی نگه میدارد که فقط تا ۹٬۰۰۷٬۱۹۹٬۲۵۴٬۷۴۰٬۹۹۱ دقیق است و عددهای صحیح بزرگتر گرد میشوند. چنین شناسههایی را بهشکل متن بفرستید، مثلاً "id": "12345678901234567890".
منابع و مطالعهی بیشتر
- RFC 8259 – The JavaScript Object Notation (JSON) Data Interchange Format (www.rfc-editor.org)
- ECMA-404 – The JSON data interchange syntax (2nd edition, 2017) (ecma-international.org)
- MDN Web Docs: JSON.parse() (developer.mozilla.org)
- Python documentation: json – JSON encoder and decoder (docs.python.org)