JSON Toolkit

שאלות נפוצות על תחביר ופורמטים: הערות, פסיקים בסוף ודיוק

שורה תחתונה: האם הערות מותרות, למה פסיק בסוף גורם לשגיאה, האם מפתחות צריכים גרשיים, ולמה 9007199254740993 משתנה — שתים-עשרה מסקנות, כל אחת נבדקה מול RFC 8259.

מתחילכ-12 דקות קריאהפורסם 2026-10-30עודכן 2026-10-30
עטיפה לשאלות הנפוצות על תחביר ופורמטים

שורה תחתונה: כתבו JSON רגיל לפי RFC 8259 — גרשיים כפולים, בלי הערות, בלי פסיקים בסוף, UTF-8 בלי BOM — לכל דבר שחוצה גבול תהליך; השתמשו ב-JSONC או JSON5 לקונפיגורציה שצריכה הערות והמירו חזרה בזמן ריצה; וסריאלזו מספרים שלמים מעל 2^53-1 כמחרוזות בצד השרת.

מה משותף לשאלות האלה: הן נראות כאילו "צריכות לעבוד", אבל המפרט למעשה מאוד קפדני.

1. מה נחשב ל-JSON תקין

מה ההבדל בין JSON, JSON5 ו-JSONC?

JSON סטנדרטי מכיר רק מחרוזות בגרשיים כפולים ולא מתיר הערות או פסיקים בסוף. JSON5 ו-JSONC מוסיפים שניהם תכונות על גביו:

תכונה JSON (RFC 8259) JSONC JSON5
מחרוזות בגרשיים כפולים נתמך נתמך נתמך
מחרוזות בגרשיים בודדים לא נתמך לא נתמך נתמך
הערות לא נתמך נתמך נתמך
פסיקים בסוף לא נתמך לא נתמך נתמך
מפתחות בלי גרשיים לא נתמך לא נתמך נתמך
מספרים הקסדצימליים לא נתמך לא נתמך נתמך
NaN / Infinity לא נתמך לא נתמך נתמך
סיומת קובץ .json .jsonc .json5

עקרון הבחירה פשוט: תמיד השתמשו ב-JSON סטנדרטי בין גבולות תהליכים, בעוד שקובצי קונפיגורציה יכולים להשתמש ב-JSONC או JSON5 לפי מה שהעורך שלכם תומך בו. tsconfig.json ו-.vscode/settings.json יכולים להכיל הערות כי העורך מפרס אותם כ-JSONC; העברה שלהם ל- JSON.parse קפדני עדיין תיתן שגיאה.

האם JSON יכול להכיל הערות?

לא. לדקדוק ב-RFC 8259 אין כלל ייצור להערות, כך שכל // או /* */ גורם לפירוס להיכשל.

אם רוצים לשמור הסברים בקונפיגורציה, יש שלוש חלופות:

{
  "_comment": "以下超时单位是毫秒",
  "timeout": 3000,
  "retry": {
    "$doc": "重试次数,0 表示不重试",
    "count": 3
  }
}
  • השתמשו בשדות מוסכמים כמו _comment שהקורא פשוט מתעלם מהם;
  • השתמשו בשדות עם קידומת $ כדי לצמצם את הסיכוי להתנגשות עם שדות עסקיים;
  • פשוט עברו ל-JSONC או YAML לקונפיגורציה והמירו ל-JSON סטנדרטי בזמן ריצה.

למה פסיק בסוף גורם לשגיאה?

כי בדקדוק גם מערכים וגם אובייקטים הם מבני "אלמנט + מפריד", ואסור פסיק אחרי האלמנט האחרון.

{
  "a": 1,
  "b": 2,
}

לקטע שלמעלה יש פסיק עודף לפני }, והשגיאה מדווחת בדרך כלל על הפסיק או על הסוגר המסולסל, בהתאם למימוש המפרס. זו גם המלכודת הקלה ביותר במעבר מ-JavaScript — אובייקטים ליטרליים ב-JS מתירים פסיק בסוף, JSON לא.

האם גרשיים בודדים מותרים?

לא. מחרוזות חייבות להשתמש בגרשיים כפולים; 'text' היא שגיאת תחביר.

אם הנתונים הועתקו מ-repr() של Python, מסקריפט shell או מקונפיגורציה שנכתבה ביד, המירו אותם בכלי קודם במקום לקוות שהמפרס יגלה סובלנות.

האם מפתחות יכולים להיות בלי גרשיים?

לא. שמות חברי אובייקט חייבים להיות מחרוזות עטופות בגרשיים כפולים.

{ name: "json" }

זה אובייקט ליטרלי של JavaScript, לא JSON. הוספת גרשיים הופכת אותו לתקין:

{ "name": "json" }

האם הרמה העליונה יכולה להיות מערך או מחרוזת?

כן. RFC 8259 מתיר כל ערך JSON ברמה העליונה: אובייקטים, מערכים, מחרוזות, מספרים, true, false ו-null כולם תקינים.

בפרקטיקה הנדסית, עם זאת, מומלץ להשתמש רק באובייקטים או מערכים ברמה העליונה, משתי סיבות:

  1. תאימות: מפרסים ישנים (מתקופת RFC 4627) דורשים שהרמה העליונה תהיה אובייקט או מערך;
  2. אבטחה: למערך ברמה העליונה היה היסטורית סיכון של JSON hijacking. דפדפנים מודרניים תיקנו זאת, אבל מערכות ישנות עדיין מניחות את המוסכמה.

כך ש-"hello" הוא JSON תקין וגם ["a", "b"], אבל API שמחזיר {"data": [...]} בטוח יותר.

2. קידוד ומחרוזות

האם BOM בתחילת הקובץ שובר את הפירוס?

כן. RFC 8259 דורש במפורש שטקסט JSON יועבר כ-UTF-8 ואסור להוסיף byte order mark (BOM).

ה-BOM הוא שלושת הבייטים EF BB BF ממש בתחילת הקובץ. הוא בלתי נראה לחלוטין, אבל הוא תופס את המיקום הראשון של המחרוזת, כך שהמפרס מסיק ש"התו הראשון אינו {" ומחזיר שגיאה — והשורה והעמודה המדווחות הן בדרך כלל שורה 1, עמודה 1, מה שנראה מתסכל.

"Save as UTF-8" של Windows Notepad כולל BOM כברירת מחדל. לבדיקה והסרה:

# Check: output starting with efbbbf means a BOM is present
head -c 3 data.json | xxd

# Remove: skip the first 3 bytes with tail
tail -c +4 data.json > clean.json

JSON.parse של JavaScript מודרני סובל BOM (V8 מדלג עליו פנימית), אבל מימושים קפדניים ב-Java, Go ו-Python לא, וזו סיבה נפוצה לכך ש"אותו קובץ נותן תוצאות שונות בשפות שונות".

האם תווים בסינית צריכים להיות מומרים ל-\uXXXX?

לא. RFC 8259 דורש שטקסט JSON יהיה UTF-8, כך שסינית יכולה להופיע ישירות; {"name": "张三"} תקין לגמרי.

\uXXXX הוא רק כתיב חלופי שקול, שמשמש לערוצים שלא יכולים לייצג את התו ישירות. כלל האצבע:

  • גופי בקשות HTTP, קבצים, מסדי נתונים: כתבו סינית ישירות, חוסכים 3 בייטים לכל תו;
  • כותרות HTTP, פרמטרים ב-URL, הטמעה בתוך מחרוזת אחרת: השתמשו בקידודים כדי להימנע מבעיות קידוד.

התווים היחידים שחייבים להיות מקודדים הם תווי בקרה (U+0000 עד U+001F). לדוגמה, שורה חדשה חייבת להיות כתובה כ-\n; אי אפשר לשים שורה חדשה ליטרלית בתוך מחרוזת.

האם אמוג'י יכול להיכנס ישירות למחרוזת?

כן, אבל זכרו שהוא תופס שתי יחידות קוד UTF-16.

אמוג'י והרבה תווים נדירים (כמו 𠮷) נמצאים מחוץ ל-BMP, כך שב-JavaScript "😀".length === 2. אימות לפי מספר תווים עלול אפוא לטעות בקלות:

const text = "😀";
console.log(text.length);          // 2, number of UTF-16 code units
console.log([...text].length);     // 1, number of code points
console.log(Buffer.byteLength(text)); // 4, number of UTF-8 bytes

אם מגבלת אורך כינוי מחושבת עם .length, אמוג'י אחד צורך שני משבצות; אם הבקאנד סופר בייטים, זה ארבעה. כשמתאימים הגדרות, ציינו בבירור אם הכוונה ל-code points, ל-code units או לבייטים.

3. מספרים וערכים מיוחדים

למה המספר השלם הגדול 9007199254740993 הפך ל-9007199254740992?

כי JSON.parse של JavaScript ממיר מספרים לצפים מדויקים כפולים של IEEE 754, והמספר השלם הבטוח הגדול ביותר הוא 2^53-1.

Number.MAX_SAFE_INTEGER === 9007199254740991  // true
9007199254740993                              // actually yields 9007199254740992

כל מספר מעל 2^53-1 עלול להיות מעוגל בספרות האחרונות שלו. קורבנות טיפוסיים הם מזהי משתמש בני 19 ספרות שנוצרים באלגוריתם snowflake, מספרי הזמנה וחותמות זמן בנסיבות.

שלושה תיקונים, לפי עדיפות:

  1. סריאלזו את המזהה כמחרוזת בצד השרת: "id": "9007199254740993", ומסירים את בעיית הדיוק בשורשה;
  2. השתמשו בספריית פירוס ללא אובדן: תחליפים ל-JSON.parse יכולים לשמר מספרים שלמים גדולים כהוויתם, לדוגמה json-bigint;
  3. חלצו עם ביטוי רגולרי לצורך תצוגה בלבד: אל תפרסו את כל המסמך, פשוט התאימו את השדה המבוקש כטקסט.

שימו לב של-JSON.stringify יש בעיה סימטרית: מספר מחוץ לטווח השלמים הבטוח עלול כבר לא להחזיק את ערכו המקורי לאחר סריאליזציה.

האם NaN ו-Infinity יכולים להיכנס ל-JSON?

לא. כלל הייצור של מספרי JSON מתיר רק סימון עשרוני, ו-NaN, Infinity ו--Infinity אינם ערכי JSON תקינים.

הגישה של JavaScript היא "השפלה שקטה":

JSON.stringify({ value: NaN });        // '{"value":null}'
JSON.stringify({ value: Infinity });   // '{"value":null}'

אין שגיאה, NaN פשוט הופך ל-null. זו הסצנה הקלאסית שבה "החזית שלחה NaN והבקאנד קיבל null". כשצריך להעביר סמנטיקה כזו, בטאו אותה במפורש בעזרת מחרוזות או ערכי sentinel מוסכמים:

{ "ratio": null, "ratioState": "not-a-number" }

האם אפשר לכתוב מספרים כ-1e3 או 0.5?

כן. מספרי JSON תומכים בעשרוניות ובחזקות, כך ש-1e3, 1E3, 0.5 ו--0.5 כולם תקינים.

אבל יש מגבלות:

  • הם לא יכולים להתחיל או להסתיים ב-.: .5 ו-5. שניהם לא תקינים, אז כתבו 0.5 ו-5;
  • בלי אפסים מובילים: 01 לא תקין;
  • בלי הקסדצימלי: 0x1F לא תקין, זו הרחבה של JSON5;
  • -0 תקין, אבל JSON.stringify(-0) מחזיר 0, ומאבד את הסימן.

4. סמנטיקת אובייקטים

מה קורה עם מפתחות כפולים?

ההתנהגות מוגדרת על ידי המימוש; RFC 8259 רק אומר ששמות מפתחות "אמורים" להיות ייחודיים ואינו מפרט מה קורה בכפילויות.

בפועל הרוב המכריע של המימושים לוקחים את הערך האחרון:

JSON.parse('{"a":1,"a":2}')  // { a: 2 }

אבל אסור להסתמך על זה. חלק מספריות Java, encoding/json של Go ו-json של Python שונים בין גרסאות: חלק לוקחים את הערך הראשון, חלק מחזירים שגיאה וחלק לוקחים את האחרון. התייחסות למפתחות כפולים כשגיאה היא הגישה הבטוחה ביותר בין שפות.

אי אפשר למצוא מפתחות כפולים בעין — ב-JSON ממוזער אותו מפתח עשוי להיות במרחק אלפי תווים. השתמשו בסקריפט:

# Extract keys that appear more than once at the same level, as a rough check
jq -r 'paths(scalars) | join(".")' data.json | sort | uniq -d

למה undefined הופך?

זה תלוי, וזו הנקודה המועדת ביותר לבאגים:

JSON.stringify({ a: undefined });              // '{}'  the key is dropped entirely
JSON.stringify({ a: undefined, b: 1 });        // '{"b":1}'
JSON.stringify([undefined]);                   // '[null]'  becomes null inside arrays
JSON.stringify({ a: function () {} });         // '{}'  functions are dropped too

אותו undefined פירושו "השדה נעלם" בתוך אובייקט ו"הופך ל-null" בתוך מערך. אם הבקאנד נוקט ענפים שונים עבור "שדה חסר" ו"שדה הוא null", מקבלים כאן חוסר עקביות.

כשצריך לבטא "אין ערך" במפורש, השתמשו ב-null ולא ב-undefined.

מה קורה אחרי פירוס של {"__proto__": {...}}?

JSON.parse עצמו בטוח: __proto__ מטופל כמאפיין עצמי רגיל ואינו משנה את הפרוטוטיפ.

הדבר המסוכן הוא פעולת המיזוג אחרי הפירוס:

const patch = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, patch);       // some implementations pollute the prototype

הגישה הנכונה היא לעבור על מאפיינים עצמיים ולדלג על __proto__, constructor ו-prototype. פרטים בשאלות נפוצות על ביצועים ואבטחה.

5. עיון מהיר

תחביר תקין הערות
{"a": 1,} לא פסיק בסוף
{'a': 1} לא גרשיים בודדים
{a: 1} לא מפתח בלי גרשיים
{"a": 1} // comment לא הערות
"hello" כן מחרוזת ברמה העליונה תקינה, אבל נדיר להשתמש בה ב-API
{"a": 1e3} כן חזקות נתמכות
{"a": 01} לא אפס מוביל
{"a": NaN} לא ליטרל לא תקין; stringify הופך אותו ל-null
\uFEFF{"a":1} לא BOM אינו חלק מטקסט JSON
{"a":1,"a":2} שנוי במחלוקת מימושים לוקחים את הערך האחרון, אבל אל תסתמכו על זה