JSON إلى TypeScript
يولّد واجهات TypeScript من JSON: يدمج عناصر المصفوفة، ويعلّم المفاتيح الاختيارية، ويوحّد الأنواع المختلطة، ويُبقي null منفصلًا — كل ذلك في متصفحك.
export interface Root {
id: number
name: string
active: boolean
address: Address
roles: string[]
posts: Post[]
tags: unknown[]
}
export interface Address {
city: string
zip: null
}
export interface Post {
id: number
title: string
views: number | string
pinned?: boolean
}
الواجهات: 3
أنواع مستنتجة من البيانات، لا مخمَّنة
لا يحمل مستند JSON أنواعه الخاصة — بل يحمل قِيَمًا، وعلى الأنواع أن تُقرأ منها. هذا سهل لكائن واحد ودقيق على نحو مفاجئ لمجموعة: الشكل الذي تريده ليس شكل سجل بعينه، بل الشكل الذي يجب أن يحققه كل سجل. تستنتج هذه الأداة واجهات TypeScript من عيّنة JSON بالنظر إلى كل ما تحتويه العيّنة، فتصف الأنواع التي تنتجها بياناتك بأكملها لا الصف الأول الذي صادف أن كان في الأعلى.
التحويل في اتجاه واحد فقط. إعادة تحويل الأنواع إلى JSON تعني اختلاق قِيَم، والهدف هنا معاكس: المستند الملصوق هو مصدر الحقيقة، وكل تصريح مشتق منه. الصق استجابة من واجهة برمجية أو ملف إعدادات أو سطر سجل، واقرأ الواجهة التي كنت ستكتبها يدويًا لولا ذلك.
كيف تُدمج المصفوفات
القرارات المثيرة كلها تحدث عند المصفوفات. المحوِّل الساذج ينظر إلى العنصر الأول ويتوقف، فيجعل كل حقل يراه صدفةً يبدو إلزاميًا ويُفوِّت كل حقل لا يراه. تدمج هذه الأداة بدلًا من ذلك كل العناصر في نوع واحد، وينتج عن ذلك ثلاثة أمور:
- مفتاح موجود في بعض العناصر وغائب عن أخرى يصبح اختياريًا، يُكتب بعلامة استفهام. إن كان لنصف سجلاتك "middleName" ولنصفها لا، فالحقل هو "middleName?"، وهو بالضبط ما على المستهلك أن يتعامل معه.
- مفتاح يختلف نوع قيمته بين العناصر يصبح اتحادًا. حقل هو رقم في سجل ونص في آخر يُصنَّف "number | string" — لا من باب الترتيب، بل لأن هذا ما تحتويه البيانات فعلًا وما على شيفرتك أن تقبله.
- كائن متداخل داخل العناصر يُدمج بالطريقة نفسها، تعاوديًا، ويُستخرَج إلى واجهته الخاصة. عشرة عناصر مصفوفة يحمل كل منها "address" تنتج واجهة Address واحدة تصف العشرة جميعًا.
حين يكون المستند نفسه مصفوفةً على المستوى الأعلى، يصبح الجذر اسمًا مستعارًا لواجهة عنصر — مثل "type Root = RootItem[]" — مع نوع العنصر المدموج مكتوبًا تحته.
null والاختياري ولماذا يختلفان
من المغري معاملة null معاملة المفتاح الغائب، وهذا خطأ. في TypeScript يعني "name?: string" أن الخاصية قد تغيب؛ ويعني "name: string | null" أنها موجودة دائمًا لكنها قد تحمل null. هذان عقدان مختلفان، والمستهلك يفحصهما بطريقتين مختلفتين — "in" مقابل مقارنة قيمة. تُبقي هذه الأداة بينهما فصلًا: null صريح في البيانات يصبح عضو اتحاد "| null"، موضوعًا أخيرًا كي يُقرأ "string | null" كما تتوقع، بينما مفتاح يغيب ببساطة عن بعض السجلات يصبح اختياريًا. حقل هو الاثنان معًا — null في سجل، وغائب في آخر — يخرج كليهما، "field?: T | null"، لأن كلتا الحقيقتين صحيحتان بشأن بياناتك.
الكائنات المتداخلة تصير واجهات مسمّاة
بدل تضمين شكل متداخل داخل أصله، يُستخرَج كل كائن إلى واجهته الخاصة التي يُشتق اسمها من المفتاح الذي يقع تحته. كائن "user" يصير واجهة User؛ و"address" بداخله يصير واجهة Address تشير إليها User. الأنواع المضمَّنة بعمق يصعب قراءتها ويستحيل إعادة استخدامها، والواجهات المسمّاة هي ما كنت ستكتبه بنفسك. تُفرَد عناصر المصفوفة حين يمكن — "users" تعطي User، و"categories" تعطي Category — ومفتاح لا يُجمَع يأخذ اللاحقة Item كي يكون للعنصر اسمه الخاص.
إذا كان كائنان مختلفان سيأخذان الاسم نفسه — "data" غير مترابطين مثلًا — يأخذ الثاني لاحقةً بدل أن يُدمج، فتبقى الأشكال المتمايزة متمايزة. يُخرَج كائن الجذر أولًا ويمكنك إعادة تسميته؛ والاختيار بين مخرجات "interface" و"type" مفتاح، لأن بعض قواعد الشيفرة تفضّل الأسماء المستعارة للأنواع في كل مكان.
التراجع إلى unknown
بعض القيم لا تحمل أي معلومة نوع. مصفوفة فارغة قد تحتوي أي شيء؛ وكائن فارغ لا مفاتيح له لوصفها. بدل اللجوء إلى "any" — الذي يُطفئ فحص الأنواع لكل ما يليه — تتراجع الأداة إلى "unknown": مصفوفة فارغة تصير "unknown[]"، وكائن فارغ يصير "Record<string, unknown>". الفرق مهم. "any" يمرِّر العلل بصمت؛ و"unknown" يُجبر المستهلك على تضييق القيمة قبل استخدامها، فيبقى النوع المستنتَج صادقًا بشأن ما قالته العيّنة وما لم تقله.
هذه هي الأنواع التي ستُحكِمها يدويًا حالما تعرف ما ينبغي أن تحتويه المجموعة الفارغة — لكن إلى أن تقول البيانات ذلك، "unknown" هو الجواب الصادق، وهو الذي يُبقي بقية أنواعك آمنة.
على ماذا يعمل، وأين
كل شيء يحدث في متصفحك. يُحلَّل JSON وتُستنتَج الأنواع على جهازك؛ لا شيء مما تلصقه يُرفَع أو يُخزَّن أو يُسجَّل. هذا يجعل الأداة آمنة على استجابة واجهة برمجية حقيقية أو ملف إعدادات فيه أسرار: العيّنة لا تغادر الصفحة أبدًا. المخرجات TypeScript عادي يمكنك لصقه مباشرةً في ملف "d.ts" أو وحدة، وضبط حقول "unknown" القليلة التي عجزت البيانات عن وصفها، واستخدامه.
الأسئلة الشائعة
- هل يُرسَل JSON الخاص بي إلى خادم؟
- لا. يُحلَّل المستند وتُستنتَج الأنواع بالكامل في متصفحك، ولا شيء مما تلصقه يُرفَع أو يُسجَّل. آمن للاستخدام على استجابة واجهة برمجية حقيقية أو ملف إعدادات.
- لماذا يكون حقل اختياريًا رغم وجوده في عيّنتي؟
- لأنه غائب عن عنصر واحد على الأقل من مصفوفة دمجتها الأداة. يقرأ الاستنتاج كل عنصر لا الأول فقط، فمفتاح تُسقطه بعض السجلات يصبح اختياريًا — إنه النوع الذي تدعمه بياناتك فعلًا، وإن كان السجل الذي نظرت إليه قد تضمّنه صدفة.
- لماذا صار حقل اتحادًا مثل string | number؟
- لأن القيمة كانت من أنواع مختلفة في عناصر مصفوفة مختلفة — نص في سجل ورقم في آخر. على النوع المدموج أن يقبل الاثنين، فيُكتب اتحادًا. إن فاجأك ذلك، فهو غالبًا يعني أن البيانات أقل انتظامًا مما توقعت، وهذا جدير بالمعرفة.
- لماذا تستخدم الأداة unknown بدل any؟
- للقيم التي لا تستطيع وصفها — مصفوفة فارغة، كائن فارغ — يُبقي "unknown" النتيجة آمنة نوعيًا، ويُجبر المستهلك على تضييق القيمة قبل استخدامها، بينما "any" سيُطفئ فحص الأنواع. يمكنك إحكام هذه الحقول يدويًا حالما تعرف ما تحتويه المجموعة الفارغة.
- ما الفرق بين مخرجات interface و type؟
- لا فرق في الأنواع التي تصفها — كلاهما ينتج الأشكال نفسها. "interface" هو الاصطلاح الشائع لأنواع الكائنات ويمكن توسيعه ودمجه؛ وأسماء "type" المستعارة هي ما تفضّل بعض قواعد الشيفرة استخدامه في كل مكان. المفتاح موجود كي تطابق المخرجات أسلوب مشروعك.
- هل يمكنه تحويل TypeScript إلى JSON؟
- لا، وعن قصد. التحويل باتجاه واحد: JSON داخل، أنواع خارج. الذهاب في الاتجاه الآخر يعني اختلاق قِيَم لم تكن قط في بياناتك، وجوهر الأمر أن كل تصريح مشتق مما لصقته فعلًا.
- كيف تُسمّى الكائنات المتداخلة؟
- من المفتاح الذي تقع تحته: كائن "user" يصير User، و"address" بداخله يصير Address. تُفرَد عناصر المصفوفة حين يمكن — "categories" تعطي Category — ومفتاح لا يُجمَع يأخذ اللاحقة Item. شكلان مختلفان سيتصادمان على اسم يأخذان لاحقةً بدل أن يُدمجا، فيبقيان متمايزين.