javascript - Kuinka jäsentää JSON-merkkijono TypeScriptissä


Onko olemassa tapa käsitellä merkkijonoja JSONina TypeScriptissä?

Esimerkiksi JavaScriptissä voimme käyttää JSON.parse(). Onko TypeScriptissä olemassa vastaavaa funktiota?

Minulla on JSON-objektimerkkijono seuraavasti:

{"name": "Bob", "error": false}

Vastaus #1

TypeScript on (JavaScriptin yläjoukko), joten käytät vain JSON.parse kuten tekisit JavaScriptissä:

let obj = JSON.parse(jsonString);

Vain että TypeScriptissä voit myös määrittää tyypin tuloksena olevalle objektille:

interface MyObj {
    myString: string;
    myNumber: number;
}

let obj: MyObj = JSON.parse('{ "myString": "string", "myNumber": 4 }');
console.log(obj.myString);
console.log(obj.myNumber);

(koodi leikkikentässä)

Vastaus #2

Tyyppiturvallinen JSON.parse

Voit jatkaa JSON.parse-funktion käyttöä, sillä TypeScript on JavaScriptin yläjoukko:

Tämä tarkoittaa, että voit ottaa minkä tahansa toimivan JavaScript-koodin ja laittaa sen TypeScript-tiedostoon ilman, että sinun tarvitsee huolehtia tarkalleen siitä, miten se on kirjoitettu.

Jäljelle on jäänyt ongelma: JSON.parse palauttaa any, mikä heikentää tyyppiturvallisuutta (älä käytä any).

Tässä on kolme ratkaisua vahvemmille tyypeille, järjestettynä kasvavan monimutkaisuuden mukaan:

1. Käyttäjän määrittelemät tyyppisuojaukset

Leikkikenttä

// For example, you expect to parse a given value with `MyType` shape
type MyType = { name: string; description: string; }

// Validate this value with a custom type guard (extend to your needs)
function isMyType(o: any): o is MyType {
  return "name" in o && "description" in o
}

const json = '{ "name": "Foo", "description": "Bar" }';
const parsed = JSON.parse(json);
if (isMyType(parsed)) {
  // do something with now correctly typed object
  parsed.description
} else { 
// error handling; invalid JSON format 
}

isMyType kutsutaan tyyppisuojaksi. Sen etuna on, että saat täysin tyypitetyn objektin totuusarvoltaan if-haaraan.

2. Yleinen JSON.parse-kääre

Leikkikenttä

Luo geneerinen kääre JSON.parse -funktion ympärille, joka ottaa yhden tyypintarkistuksen syötteenä ja palauttaa jäsennellyn, tyypitetyn arvon tai virhetuloksen:

const safeJsonParse = <T>(guard: (o: any) => o is T) => 
  (text: string): ParseResult<T> => {
    const parsed = JSON.parse(text)
    return guard(parsed) ? { parsed, hasError: false } : { hasError: true }
  }

type ParseResult<T> =
  | { parsed: T; hasError: false; error?: undefined }
  | { parsed?: undefined; hasError: true; error?: unknown }

Käyttöesimerkki:

const json = '{ "name": "Foo", "description": "Bar" }';
const result = safeJsonParse(isMyType)(json) // result: ParseResult<MyType>
if (result.hasError) {
  console.log("error :/")  // further error handling here
} else {
  console.log(result.parsed.description) // result.parsed now has type `MyType`
}

safeJsonParse voitaisiin laajentaa epäonnistumaan nopeasti tai käsittelemään try/catch JSON.parse -virheitä.

3. Ulkoiset kirjastot

Kirjoittamalla tyypin suojafunktioita käsin tulee työlääksi, jos sinun täytyy validoida monia eri arvoja. On kirjastot, jotka auttavat tässä tehtävässä - esimerkkejä (ei kattava lista):

  • io-ts: vaatii fp-ts-riippuvuuden, käyttää funktionaalista ohjelmointityyliä
  • zod: pyrkii olemaan enemmän proseduraalinen/oliopohjainen kuin io-ts
  • typescript-is: TS-muunnin käännösohjelman API:lle, lisäkerros kuten ttypescript tarvitaan
  • typescript-json-schema/ajv: Luo JSON-skeema tyypeistä ja validoi se ajv:llä

Lisätietoja

  • Suoritusaikainen tyypin tarkistus #1573
  • Rajapintatyypin tarkistus Typescriptillä
  • TypeScript: ulkoisen datan validointi

Vastaus #3

Jos haluat JSON:isiin olevan validoitu Typescript-tyyppi, sinun täytyy tehdä tuo validointityö itse. Tämä ei ole mitään uutta. Tavallisessa Javascriptissä sinun täytyisi tehdä sama asia.

Vahvistus

Pidän siitä, että ilmaisen validointilogiikkani joukolla "muunnoksia". Määrittelen Descriptor:n muunnosten karttana:

type Descriptor<T> = {
  [P in keyof T]: (v: any) => T[P];
};

Sitten voin tehdä funktion, joka soveltaa näitä muunnoksia mielivaltaiseen syötteeseen:

function pick<T>(v: any, d: Descriptor<T>): T {
  const ret: any = {};
  for (let key in d) {
    try {
      const val = d[key](v[key]);
      if (typeof val !== "undefined") {
        ret[key] = val;
      }
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      throw new Error(`could not pick ${key}: ${msg}`);
    }
  }
  return ret;
}

Nyt en vain validoi JSON-syötettäni, vaan myös rakennan Typescript-tyyppiä samalla kun etenen. Yllä olevat geneeriset tyypit varmistavat, että tulos päättelee tyypit "muunnoksistasi".

Jos muunnos aiheuttaa virheen (mikä on tapa toteuttaa validointi), pidän siitä, että käännän sen toisen virheen ympärille osoittaen, mikä avain aiheutti virheen.

Käyttö

Esimerkissäsi käyttäisin tätä seuraavasti:

const value = pick(JSON.parse('{"name": "Bob", "error": false}'), {
  name: String,
  error: Boolean,
});

Nyt value tulee olemaan tyypitetty, koska String ja Boolean ovat molemmat "muuntajia" siinä mielessä, että ne ottavat syötteen ja palauttavat tyypitetyn tuloksen.

Lisäksi value tulee todellisuudessa olemaan tuota tyyppiä. Toisin sanoen, jos name olisi todellisuudessa 123, se muunnetaan "123", jotta sinulla on kelvollinen merkkijono. Tämä johtuu siitä, että käytimme String-funktiota ajon aikana, sisäänrakennettua funktiota, joka hyväksyy mielivaltaisen syötteen ja palauttaa string-tyyppisen arvon.

Voit nähdä tämän toimivan täällä. Kokeile seuraavia asioita vakuuttuaksesi itse:

  • Siirrä hiiri const value -määritelmän päälle nähdäksesi, että pop-up näyttää oikean tyypin.
  • Yritä muuttaa "Bob" muotoon 123 ja suorita esimerkki uudelleen. Konsolistasi näet, että nimi on oikein muunnettu merkkijonoksi "123".

Aiheeseen liittyviä artikkeleita