يعيد JSON.parse النوع any، لذلك يقبل TypeScript أي نوع تسند إليه النتيجة. هذا يجعل تحديد نوع JSON المحلَّل سطرًا واحدًا، ويعني أيضًا أن النوع وعد تقطعه أنت، لا شيء يفحصه المترجم:
في الكائن الثاني تكون age هي النص "41". ومع ذلك يسميها TypeScript number، لأن any يمكن إسنادها إلى أي شيء، ويطبع البرنامج 411. لـ JSON كتبه كودك نفسه قبل لحظة، تحديد النوع لا بأس به. أما للبيانات القادمة من طلب أو ملف أو من التخزين المحلي، فتحقق منها.
التحليل إلى unknown
تحديد نوع النتيجة كـ unknown يجعل المترجم يصر على فحص قبل استخدام أي خاصية:
الخطأ هو index.ts(4,13): error TS18046: 'data' is of type 'unknown'. وكل فحص تكتبه بعد ذلك يضيّق نوع data أكثر قليلًا.
التحقق بـ Type Guard
الـ type guard دالة تعيد value is User. عندما تعيد true يعامل TypeScript القيمة كـ User من تلك النقطة، والفحوص داخلها فحوص حقيقية وقت التشغيل:
يرمي JSON.parse نفسه SyntaxError مع النص المشوّه، لذلك يغلّفه الكود الحقيقي بـ try/catch أيضًا. مع البيانات الكبيرة أو المتداخلة تطول الحراسات المكتوبة يدويًا؛ ومكتبات المخططات مثل Zod أو Valibot تتيح لك التصريح بالشكل مرة واحدة واشتقاق أداة التحقق ونوع TypeScript معًا منه.
تحويل JSON إلى Interface في TypeScript
تحويل عينة JSON إلى أنواع عمل آلي. لهذه الاستجابة:
{
"id": 42,
"title": "Learn TypeScript",
"done": false,
"owner": { "id": 7, "name": "Ada" },
"tags": ["study", "ts"],
"dueDate": "2024-03-15T10:30:00Z",
"notes": null
}
اربط كل قيمة بنوعها، وأعطِ الكائنات المتداخلة interface خاصة بها، وميّز ما يمكن أن يتغير:
لا تستطيع عينة واحدة أن تخبرك بالحقول الاختيارية أو التي تقبل null. انظر في عدة استجابات، أو في توثيق الواجهة البرمجية، قبل أن تستقر على ? و| null.
التواريخ والـ Reviver
ليس في JSON نوع للتاريخ، لذلك تصل التواريخ كنصوص. الوسيط الثاني لـ JSON.parse، الـ reviver، يُستدعى لكل مفتاح ويستطيع إعادة بنائها:
المعامل value في الـ reviver نوعه any، وكذلك النتيجة، لذلك ما زال النوع Order موثوقًا به لا مفحوصًا. يضيف JSON.stringify(order, null, 2) مسافة بادئة من مسافتين للناتج، ويعيد Date إلى نص ISO الخاص به.
JSON.stringify وما يفقده
نوع إعادة JSON.stringify هو string. والقيم التي يحوّلها لا تعود دائمًا كما كانت، ولا يحذّرك النوع:
| القيمة | بعد JSON.stringify |
|---|---|
Date | نص ISO (عبر الدالة toJSON الخاصة به) |
Map، Set | {} (حوّلها أولًا بـ [...set] أو Object.fromEntries(map)) |
undefined والدوال والرموز داخل كائن | يُحذف المفتاح |
undefined والدوال والرموز داخل مصفوفة | null |
undefined أو دالة أو رمز وحده | undefined، لا نص |
NaN، Infinity | null |
bigint | يرمي TypeError |
قواعد وقت التشغيل هي نفسها كما في JavaScript العادية، ومشروحة في JSON في JavaScript.
نوع لأي قيمة JSON
عندما يتعامل الكود مع JSON عشوائي، يصف نوع تعاودي بالضبط ما يمكن أن يحتويه JSON ويرفض القيم التي لا يمكن أن يحتويها:
دون التعليق يكون السطر الأخير خطأ ترجمة لأن كائن Date ليس JsonValue.
استيراد ملفات .json
يمكن استيراد ملف .json كوحدة، ويستنتج TypeScript نوعه من محتواه:
{ "name": "app", "port": 8080, "tags": ["a"] }
// CommonJS output, or a bundler
import config from "./config.json";
const port: number = config.port; // typed from the file: number
// An ES module under module: nodenext
import settings from "./config.json" with { type: "json" };
في TypeScript 7 يعمل هذا دون إعدادات إضافية عندما يكون module مضبوطًا على nodenext أو node20 أو commonjs أو esnext أو preserve. مع node16 وnode18 يفشل بالخطأ TS2732، Cannot find module './config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension.، إلى أن تضيف "resolveJsonModule": true؛ وضبطه على false يعطّل استيراد JSON في كل مكان. في وحدة ES مع nodenext أو node20 يحتاج الاستيراد إلى السمة with { type: "json" } (الخطأ TS1543 دونها)، ولا يُسمح إلا بالاستيراد الافتراضي (الخطأ TS1544 مع import { port }). وينسخ tsc ملف .json المستورد إلى outDir بجوار JavaScript المُترجم.
الأسئلة الشائعة
ما النوع الذي يعيده JSON.parse في TypeScript؟
any. لا يستطيع المترجم معرفة محتوى نص ما، لذلك يُترجم const user: User = JSON.parse(text) مهما كان في النص. أسند النتيجة إلى unknown وتحقق منها عندما تأتي البيانات من خارج برنامجك.
كيف أحوّل JSON إلى interface في TypeScript؟
خذ عينة ممثِّلة واكتب خاصية لكل مفتاح: النصوص والأرقام والقيم المنطقية تقابلها string وnumber وboolean، والكائن المتداخل يصبح interface مستقلة، ومصفوفة الكائنات تصبح Item[]، والمفاتيح التي تغيب أحيانًا تأخذ ?. أدوات توليد الكود مثل quicktype تؤتمت ذلك، لكن تحقق من تخميناتها بأكثر من عينة واحدة.
كيف أستورد ملف JSON في TypeScript؟
يعمل import config from "./config.json"; في TypeScript 7 عندما يكون module مضبوطًا على nodenext أو node20 أو commonjs أو esnext أو preserve، ويُحدد نوع النتيجة من محتوى الملف. مع node16 أو node18 اضبط أيضًا "resolveJsonModule": true. وفي وحدة ES مع nodenext أو node20 أضف السمة التي تطلبها Node: import config from "./config.json" with { type: "json" };.
هل يعيد JSON.stringify نصًا دائمًا؟
نوعه يقول string، لكن JSON.stringify(undefined) وJSON.stringify(() => 1) يعيدان undefined وقت التشغيل. والقيم داخل الكائنات تُحوَّل أيضًا: يصبح Date نص ISO، ويصبح Map وSet الكائن {}.