التسلسل - TypeScript: فك التسلسل من JSON إلى الاتحاد المميز


بالنظر إلى مقتطف كود Typescript:

class Excel {
    Password: string;
    Sheet: number;
}

class Csv {
    Separator: string;
    Encoding: string;
}

type FileType = Excel | Csv

let input = '{"Separator": ",", "Encoding": "UTF-8"}';

let output = Object.setPrototypeOf(JSON.parse(input), FileType.prototype)   // error!

في TypeScript/JavaScript، لعمل تحويل من JSON إلى كائن، يمكن استخدام Object.setPrototypeOf()، حيث يتطلب الوسيط الثاني "prototype". مع الكائنات (classes)، مثل Excel، يمكن ببساطة استخدام Excel.prototype. ولكن مع الاتحاد المميز (Discriminated Union) كما هو موضح أعلاه، واجهت خطأ:

error TS2693: 'FileType' only refers to a type, but is being used as a value here.

سؤال :

  1. هل هناك أي طريقة لإلغاء تسلسل اتحاد مميز في TypeScript؟
  2. إذا لم يكن كذلك، هل هناك أي طريقة أنيقة لتحقيق السيناريو أعلاه (مع إعطاء فئتين: Excel/Csv وسلسلة JSON لتسلسل أي منهما؛ واسترجاع الكائن المنشأ بشكل صحيح)، بغض النظر عن أي حيل، فئات، وراثة الفئات، واجهة، اتحاد مميز أم لا...؟

البيئة

  • تايب سكريبت الإصدار 2.9.2
  • فيجوال ستوديو كود الإصدار 1.25.1

محاولتي

let json = JSON.parse(input);
let output: FileType | null = null;
if (json["Separator"]) {
    console.log("It's csv");
    output = Object.setPrototypeOf(json, Csv.prototype)
} else if (json["Password"]) {
    console.log("It's excel");
    output = Object.setPrototypeOf(json, Excel.prototype)
} else {
    console.log("Error");
}

من السهل التعرف على أن هذا النهج مرهق (if else كثيرًا)، خاصة عند إضافة فئات جديدة. بالإضافة إلى ذلك، يجب على المطورين اختيار حقل فريد للتحقق في كل فئة...

الإجابة رقم 1

في مثالِك، FileType ليست فئة، إنها مجرد نوع اتحاد في وقت الترجمة. لا يتم إنشاء أي رمز وقت التشغيل لـ FileType، لذلك بينما يفهم فاحص النوع ما يعنيه، لا يوجد كائن FileType معرف أثناء وقت التشغيل لاسترجاع الخاصية protoptype منه.

لست واضحًا لماذا تحتاج إلى تعيين النموذج الأولي للكائن المستعاد من التسلسل في المقام الأول. لماذا لا تكتفي فقط بالإعلان عنه بهذه الطريقة:

let output = JSON.parse(input) as FileType;
if (IsExcel(output)) { /* do stuff with output.Password & .Sheet */ }
else { /* do stuff with output.Seperator and .Encoding }

تستخدم IsExcel() لتحديد ما إذا كان الكائن الذي تم إلغاء تسلسله من نوع Excel، ينبغي كتابته كحارس نوع معرف من قبل المستخدم، لكنه قد يكون شيئًا مثل

function IsExcel(f: FileType): f is Excel { . . . } 

إيسإكسل تُعيد قيمة بوليانية، ولكن بكتابة نوع الإرجاع بهذه الطريقة، يفهم TypeScript أنه يقرأ المميز للوحدة المميزة. يمكنك التحقق بأي طريقة تريدها، على سبيل المثال من خلال التحقق مما إذا كان (<any>f).Sheet معرفًا.