serialisering - TypeScript: Deserialiser fra JSON til diskriminert union


Gitt Typescript-kodesnutten:

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!

I TypeScript/Javascript, for å deserialisere fra JSON, kan man bruke Object.setPrototypeOf(), hvor den andre parameteren krever en "prototype". Med klasser, f.eks. Excel, kan man bare gjøre Excel.prototype. Men med Discriminated Union som ovenfor, støtte jeg på en feil:

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

Spørsmål :

  1. Finnes det en måte å deserialisere en diskriminert union i TypeScript på?
  2. Hvis ikke, finnes det noen annen elegant måte å realisere scenariet ovenfor på (gitt to klasser: Excel/Csv og JSON-streng som serialiserer en av dem; få tilbake korrekt instansiert objekt), uansett hvilke triks, klasser, klassearv, grensesnitt, diskriminert union eller ikke...?

Miljø

  • Typescript v2.9.2
  • Visual Studio Code v1.25.1

Mitt forsøk

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");
}

Det er lett å gjenkjenne at denne tilnærmingen er tungvint (if else mye), spesielt når man legger til nye klasser. I tillegg må utviklere velge et unikt felt for sjekking i hver klasse...

Svar #1

I eksemplet ditt er FileType ikke en klasse, det er bare en samlingstype som eksisterer under kompilering. Det genereres ingen kjørbar kode for FileType, så selv om typekontrolleren forstår hva det betyr, finnes det ingen FileType-objekt definert under kjøring for å hente prototype-egenskapen fra.

Jeg forstår ikke helt hvorfor du må sette prototypen til det deserialiserte objektet i utgangspunktet. Hvorfor ikke bare erklære det på denne måten:

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() er for å avgjøre om det deserialiserte objektet er av Excel-typen, det bør skrives som en Brukerdefinert Typevakt, men det kan være noe som

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

IsExcel returnerer en boolsk verdi, men ved å skrive returtypen på denne måten forstår TypeScript at den leser diskriminatoren for den diskriminerte unionen. Du kan sjekke på hvilken som helst måte du vil, for eksempel ved å sjekke om (<any>f).Sheet er definert.

Relaterte artikler