JSON Toolkit

یک JSON، چهار ابزار، چهار نتیجه: چگونه فرمت‌کننده انتخاب کنیم

جمع‌بندی: فرمت کردن بی‌اتلاف نیست. همان JSON با صفر پیشرو، BOM یا عدد ۲۰ رقمی در Python، Node، jq و json.tool نتایج متفاوتی می‌دهد: برخی خطا می‌دهند و برخی بی‌صدا مقدار را تغییر می‌دهند. ده نمونه اجرا شده روی چهار مسیر، جدول انتخاب و یک اسکریپت اعتبارسنجی سختگیرانه به دست می‌دهد.

Intermediateحدود 6 دقیقه مطالعهانتشار 2026-11-03به‌روزرسانی 2026-11-03
چهار مسیر پردازش نتایج متفاوتی برای یک JSON نشان می‌دهند

نتیجه: خطرناکترین تفاوت میان ابزارهای فرمت JSON تعداد فاصلههای تورفتگی نیست، بلکه این است که یک ورودی واحد در ابزاری خطا میدهد و در ابزاری دیگر بیصدا دادهها را تغییر میدهد. پیش از انتخاب، یک نمونه با مقدار مرزی از ابزار عبور دهید — بیش از فهرست قابلیتها به شما میگوید.

کاربر گمان میکند فرمت کردن یعنی «دوباره چیدن متن». در واقع هر مسیر متن را با تجزیهگر خودش از نو تفسیر میکند. وقتی تجزیهگر سهلگیر باشد، خطایی که باید پرتاب شود به تغییر خاموش یک مقدار بدل میشود — در حالی که صفحه همچنان پیام سبز آرامبخش «با موفقیت فرمت شد» را نشان میدهد.

1. پدیده: یک ورودی، چهار مسیر، چهار نتیجه

محیط آزمایش Python 3.11.1، Node v22.13.1 و jq 1.7 است. ده رشته کوتاه JSON که دستی ساخته شدند از ورودی استاندارد وارد شدند، بدون نوشتن روی دیسک و بدون تغییر پیکربندی. چهار مسیر: json.loads، JSON.parse، jq -c .، python -m json.tool. ERR یعنی پرتاب استثنا و توقف، SILENT یعنی بدون خطا اما خروجی با ورودی فرق کرده است.

نمونه python json.loads node JSON.parse jq -c . python -m json.tool
S1 {"a":1,"b":[1,2]} OK OK OK OK
S2 {"a":1,} ERR ERR ERR ERR
S3 {'a':1} ERR ERR ERR ERR
S4 {"a":01} ERR char 6 ERR position 6 SILENT {"a":1} ERR
S5 {"a":1 /*x*/} ERR char 7 ERR ERR ERR
S6 \ufeff{"a":1} ERR utf-8-sig ERR position 0 SILENT {"a":1} ERR
S7 {"id":9007199254740993} OK exact SILENT 9007199254740992 OK exact OK
S8 {"a":1,"a":2} SILENT {"a":2} SILENT {"a":2} SILENT {"a":2} OK
S9 {"a":1 ERR char 6 ERR ERR EOF ERR
S10 <html>502</html> ERR char 0 ERR Unexpected token '<' ERR ERR

چهار نمونه از ده نمونه «بدون خطا اما داده تغییر کرد» دادند: S4، S6، S7، S8. یعنی اگر فقط به چراغ سبز «فرمت موفق» اعتماد کنید، چهل درصد احتمال دارد که دیگر داده اصلی دستتان نباشد. نکته حیلهگر این است که هیچکدام از این موارد در فهرست خطاهای نحوی نیستند — فایل باز میشود، کپی میشود، خروجی گرفته میشود، و خطا در محتواست نه در نحو.

2. دلیل: لایه نحو و لایه معنا دو چیز جداگانهاند

RFC 8259 صریح میگوید: بخش ۱٫۲ توصیه میکند برخی رویههای مجاز ECMA-404 را به خاطر بیشینه همکنشپذیری کنار بگذارید؛ بخش ۴ یکتا بودن نام اشیا را فقط به صورت SHOULD میخواهد، بیآنکه رفتار کلیدهای تکراری را مشخص کند؛ بخش ۶ درباره اعداد فقط توصیه همکنشپذیری میدهد. ECMA-404 یک گام فراتر میرود و آشکارا اعلام میکند که «نسبت به معنای اعداد بیتفاوت» است — فقط نمایش دنباله ارقام را تنظیم میکند، نه ذخیرهسازی در زبان را. به عبارت دیگر، مشخصات یک ناحیه مرزی گسترده را به پیادهساز واگذار میکند.

پس «فرمت کردن» به دو لایه میشکند: لایه نحو تصمیم میگیرد متن قانونی است یا نه، لایه معنا تصمیم میگیرد متن به چه مقداری بدل شود. اولی پاسخ استاندارد دارد، دومی نه. اینکه jq مقدار 01 را ۱ میخواند و BOM آغازین را رد میکند، انتخابی در لایه معناست؛ اینکه Node یک عدد صحیح ۲۰رقمی را در double IEEE 754 جا میدهد، انتخاب زمان اجراست؛ و اینکه هر چهار مسیر برای {"a":1,"a":2} آخرین مقدار را نگه میدارند، توافق مشترک تجزیهگرهای بزرگ است. هیچکدام «قابل تجزیه بودن» را نقض نمیکنند، ولی همگی داده را تغییر میدهند.

چگونه خطاهای تجزیه JSON را رفع کنیم روی لایه نحو کار میکند و متن خطا را به مختصات ترجمه میکند. این مقاله گام بلافاصله پیش از آن را پوشش میدهد — اگر ابزار اصلاً خطایی ندهد، حتی مختصات هم به دست نمیآورید.

3. چهار تغییر خاموش داده، یکییکی

چهار نمونه خاموش را جدا کنار بگذاریم، تغییرها آنقدر کوچکاند که با یک نگاه سرسری دیده نمیشوند:

{"a": 01}                          // leading zero: jq emits {"a":1}
\ufeff{"a": 1}                     // BOM: jq skips the first three bytes
{"id": 9007199254740993}           // 20-digit int: JSON.parse gives ...992
{"a": 1, "a": 2}                   // duplicate key: all four keep {"a":2}

صفر پیشرو و BOM از نوع «ورودی از قبل نامعتبر بود، ولی ابزار سهلگیری کرد»اند. 01 در JSON استاندارد غیرقانونی است (RFC 8259 بخش ۶)، BOM هم نباید آغاز متن JSON باشد؛ Python و Node هر دو صریحاً خطا میدهند، jq ادامه دادن را انتخاب میکند. این سهلگیری هنگام پاکسازی لاگ راحت است، هنگام بررسی قرارداد فاجعه است: بالادست گمان میکند داده غلطی فرستاده که باید اصلاح شود، در حالی که پایینست از پیش نتیجهای «طبیعی به نظر میرسد» در دست دارد.

عدد صحیح بزرگ از نوع «ورودی کاملاً معتبر است، ولی زمان اجرا جایش نمیدهد» است. 9007199254740993 عدد JSON معتبری است، ولی بالاتر از 2^53-1 است؛ JSON.parse فقط 9007199254740992 را میتواند نشان دهد. int پایتون و jq مقدار اصلی را حفظ میکنند. اگر در این JSON شماره سفارش یا Snowflake ID باشد، عوض شدن یک رقم آخر یعنی حسابها دیگر جور درنمیآیند.

کلیدهای تکراری از نوع «مشخصات ساکت است، پیادهسازی خودش انتخاب میکند»اند. هر چهار مسیر بیصدا آخرین مقدار را میگیرند، بدون هیچ هشداری. RFC 8259 فقط SHOULD میگوید، پس این باگ نیست — ولی هنگام چسباندن پیکربندیها یا ادغام وصلهها سختترین ردهای است که پیدا میشود.

4. توانایی مکانیابی خطا: چه کسی سطر و ستون میدهد، چه کسی فقط یک جمله

در شش نمونه ناموفق هم میزان اطلاعات چهار مسیر خیلی فرق میکند. JSONDecodeError پایتون همزمان msg، lineno، colno و pos میدهد؛ Node v22 همراه position N (line 1 column M) گزیدهای از متن محل خطا میآورد؛ python -m json.tool از اطلاعات پایتون استفاده میکند؛ jq فقط یک جمله parse error: ... at line N میدهد، بدون انحراف نویسهای.

روی JSON تکخطی فشرده، شماره سطر و ستون عملاً بیکار است — همه چیز در سطر ۱ است. چیزی که لازم است انحراف نویسهای است. اسکریپت زیر بررسی را به «خطا + انحراف + بافت» یکدست میکند:

import json, sys

def strict_check(text: str) -> None:
    try:
        json.loads(text)
        print("OK")
    except json.JSONDecodeError as e:
        print(f"ERR {e.msg} | char {e.pos} | line {e.lineno} col {e.colno}")
        lo, hi = max(0, e.pos - 30), min(len(text), e.pos + 30)
        print("...", text[lo:hi].replace("\n", "\\n"), "...")
        sys.exit(1)

strict_check(open(sys.argv[1], encoding="utf-8").read())

این سختگیرتر از jq . است (صفر پیشرو و BOM را رد میکند) و e.pos میدهد. اینکه line 1 column 23611 چطور به جایگاه بایتی تبدیل شود و چرا انحراف نویسهای و بایتی در اسناد CJK سه برابر فرق میکنند، در اشکال‌زدایی JSON فشرده به تفصیل آمده و اینجا تکرار نمیشود.

5. اصلاح: فرمت کردن را به «بررسی سختگیرانه» + «زیباسازی» بشکنید

نتیجه ساده است: نگذارید یک دکمه «فرمت» همزمان بار بررسی و زیباسازی را به دوش بکشد. نخست با تجزیهگری سختگیر قانونی بودن را معلوم کنید، بعد ابزار چیدمان را انتخاب کنید. یک خط فرمان گام نخست را جدا میکند:

python3 -m json.tool input.json > /dev/null && echo "syntax OK" || echo "invalid syntax"
python3 -c "import json,sys; d=json.load(open(sys.argv[1],encoding='utf-8')); print(type(d).__name__, len(d))" input.json

خط اول فقط به «قابل تجزیه است؟» پاسخ میدهد، خط دوم بررسی میکند «ساختار حاصل همانی است که انتظار دارم؟». اگر دادهها عدد صحیح بزرگ دارند، با پایتون یا jq بخوانید، نه JSON.parse؛ اگر منبع ممکن است BOM داشته باشد، هنگام خواندن صریحاً encoding='utf-8-sig' بدهید نه اینکه به رد خودکار ابزار امید ببندید؛ اگر کلیدها ممکن است تکرار شوند، پیش از تجزیه جداگانه پویش کنید، چون هیچکدام از چهار مسیر هشدار نمیدهند. نوشتهشده در CI دقیقاً همان خط لولهای است که ویرایشگرها، خط فرمان و زنجیره‌های خودکارسازی توضیح میدهد، فقط یک لایه اظهار وفاداری داده بیشتر دارد.

6. چگونه انتخاب کنیم: جدول مقایسه چهار مسیر

مسیر نقطه قوت ریسک مناسب برای
python json.loads / json.tool سختگیر، سطر/ستون و انحراف نویسهای میدهد، دقت اعداد صحیح از دست نمیرود صفر پیشرو و BOM را رد میکند، رمزگذاری باید صریح رسیدگی شود بررسی قرارداد، CI، ردیابی لاگ
node JSON.parse بومی مرورگر، خطا همراه گزیدهای از متن اصلی اعداد صحیح بزرگ خاموش بریده میشوند، BOM خطا میدهد اشکالزدایی فرانتاند، ابزارهای درون مرورگر
jq -c . سازگار با لوله، سریعترین نتیجه صفر پیشرو و BOM خاموش عبور میکنند، انحراف نویسهای ندارد لولههای عملیاتی، استخراج سریع فیلد
ابزارهای مرورگر /parse، /editor نمایش درختی، مرتبسازی کلید، کپی مسیر، بدون نصب رفتار به تجزیهگر صفحه بستگی دارد، باید یک بار آزمود بررسی دستی، عیبیابی موقتی

کلید انتخاب «کدام قابلیت بیشتری دارد» نیست، بلکه «آیا این ابزار درباره دادههایم دروغ میگوید؟» است. برای دادههای دارای شماره سفارش، مبلغ یا شناسه دستگاه، پایتون یا ابزاری ترجیح دهید که صریحاً وفاداری داده را اعلام کند؛ برای صرفاً دیدن ساختار بدون ذخیره، مرورگر راحتتر است. /parse این سایت بررسی نحو و فرمت را درون مرورگر انجام میدهد — برای «اول ببینم چه شکلی است» مناسب است، ولی در مورد اعداد صحیح بزرگ بهتر است یک بار با اسکریپت بالا وارسی کنید.

7. حریم خصوصی فقط یکی از مرزهاست

«آیا دادههایم آپلود میشود؟» پرتکرارترین پرسش درباره ابزارهای آنلاین است، ولی از «آیا دادههایم تغییر میکند؟» جداست: ابزار محلی آفلاین هم میتواند عدد صحیح ۲۰رقمی را ببرد؛ برعکس، ابزار آنلاین با خطاهای سختگیرانه ممکن است متن اصلی را با POST به سرور بفرستد. دو ویژگی را باید جداگانه آزمود، یکی جای دیگری را نمیگیرد. پرسشهای حریم خصوصی سه بررسی دودقیقهای میدهد؛ این مقاله نیمه دیگر را میافزاید — با فهرست نمونههای زیر مطمئن شوید ابزار خاموش مقادیر را عوض نمیکند.

8. فهرست خودآزمایی قابل کپی

S1–S10 را به صورت cases.txt ذخیره کنید، هر خط یک نمونه، در هر ابزار نامزد بچسبانید و سه چیز را بسنجید: آیا نمونههای خطادار واقعاً خطا دادند؟ آیا خطا جای مشخصی گفت؟ آیا خروجی نمونه موفق نویسهبهنویسه با ورودی یکسان است؟ بهویژه S4، S6، S7، S8 را وارسی کنید — یک خط SILENT کافی است تا آن مسیر بهتنهایی برای اعتبارسنجی داده صالح نباشد.

سه قاعده مهندسی بیفزایید: بررسی سختگیرانه را در قلاب pre-commit بگذارید، ناسازگاری اعداد صحیح را در محصول کشف نکنید؛ فایلها را در همه زبانها به صورت UTF-8 بدون BOM بنویسید؛ برای JSON طرف سوم بدنه پاسخ خام را نگه دارید و نتیجه فرمتشده را فقط برای خواندن به کار ببرید، نه برای نوشتن دوباره.

پرسشهای پرتکرار

فرمت موفق شد، یعنی JSON سالم است؟

خیر. S4، S6، S7 و S8 همگی «پاس» شدند، ولی صفر پیشرو، BOM، عدد صحیح ۲۰رقمی و کلید تکراری بازنویسی یا بلعیده شدند. فرمت فقط ثابت میکند تجزیهگری متن را پذیرفت، نه اینکه مقادیر برابر مقادیر فرستادهشدهاند؛ فیلدهای حیاتی را مقداربهمقدار وارسی کنید.

چرا jq صفر پیشرو و BOM را میپذیرد که پایتون رد میکند؟

jq واژهکاو خودش را دارد، با اعدادی مثل 01 سهلگیر است و BOM آغازین را رد میکند؛ پایتون و Node سختگیرانه از RFC 8259 پیروی میکنند. این باگ نسخه نیست، بلکه انتخاب میان تجزیهگر سهلگیر و سختگیر است. هنگام پاکسازی اولی کار کم میکند، هنگام بررسی قرارداد دومی الزامی است.

چگونه جلوی تبدیل عدد صحیح بزرگ به 9007199254740992 را بگیرم؟

Number در جاوااسکریپت double IEEE 754 با حد امن 2^53-1 است. خواندن شناسه ۲۰رقمی با JSON.parse ناگزیر آن را میبرد. چنین شناسههایی را به صورت رشته ذخیره کنید یا از int پایتون، jq یا کتابخانه تجزیهگرِ پشتیبان BigInt بهره ببرید؛ اگر فرانتاند واقعاً به عدد نیاز دارد، پیش از تجزیه آن فیلد را با رشته جایگزین کنید.

ابزار آنلاین یا خط فرمان محلی، کدام را بگیرم؟

بر حسب حساسیت و هدف جدا کنید: فقط دیدن ساختار یا یافتن فیلد، مرورگر سریعترین است؛ ورود به خط لوله و نیاز به اظهار وفاداری داده، python -m json.tool بهعلاوه بررسی سختگیرانه. در هر دو حالت نخست S1–S10 را اجرا کنید، خطاها و خروجیها را با انتظار بسنجید، بعد تصمیم بگیرید چه ردهای از داده را وارد کنید.