javascript - Cum să analizezi un șir JSON în Typescript


Există o modalitate de a analiza șiruri ca JSON în TypeScript?

De exemplu, în JavaScript, putem folosi JSON.parse(). Există o funcție similară în TypeScript?

Am un șir de obiect JSON după cum urmează:

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

Răspuns #1

TypeScript este (un superset al) JavaScript, așa că folosești JSON.parse așa cum ai face în JavaScript:

let obj = JSON.parse(jsonString);

Doar că în TypeScript poți avea și un tip pentru obiectul rezultat:

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

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

(cod în teren de joacă)

Răspunsul #2

JSON.parse sigur din punct de vedere al tipului

Poți continua să folosești JSON.parse, deoarece TypeScript este un superset al JavaScript-ului:

Aceasta înseamnă că poți lua orice cod JavaScript funcțional și să îl pui într-un fișier TypeScript fără să-ți faci griji despre modul exact în care este scris.

A rămas o problemă: JSON.parse returnează any, ceea ce subminează siguranța tipurilor (nu folosiți any).

Iată trei soluții pentru tipuri mai puternice, ordonate după complexitate crescătoare:

1. Garduri de tip definite de utilizator

Teren de joacă

// 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 este numit un gardian de tip. Avantajul său este că obții un obiect complet tipizat în ramura if adevărată.

2. Wrapper generic JSON.parse

Teren de joacă

Creează un înveliș generic în jurul JSON.parse, care primește un singur gard de tip ca input și returnează valoarea analizată și tipizată sau rezultatul erorii:

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 }

Exemplu de utilizare:

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 ar putea fi extins pentru a eșua rapid sau pentru a încerca/prinde erorile JSON.parse.

3. Biblioteci externe

Scrierea manuală a funcțiilor de protecție de tip devine incomodă dacă trebuie să validezi multe valori diferite. Există biblioteci care pot ajuta la această sarcină - exemple (nu o listă completă):

  • io-ts: are dependență peer fp-ts, folosește stil de programare funcțional
  • zod: încearcă să fie mai procedural/orientat pe obiecte decât io-ts
  • typescript-is: transformator TS pentru API-ul compilatorului, este necesar un wrapper suplimentar precum ttypescript
  • typescript-json-schema/ajv: Creează schema JSON din tipuri și valideaz-o cu ajv

Mai multe informații

  • Verificarea tipului la rulare #1573
  • Verificarea tipului de interfață cu Typescript
  • TypeScript: validarea datelor externe

Răspunsul #3

Dacă vrei ca JSON-ul tău să aibă un tip Typescript validat, va trebui să faci tu însuți acea validare. Nu este nimic nou. În Javascript simplu, ar trebui să faci același lucru.

Validare

Îmi place să exprim logica mea de validare ca un set de „transformări”. Definiesc un Descriptor ca pe o hartă de transformări:

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

Atunci pot să creez o funcție care va aplica aceste transformări asupra oricărei intrări:

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

Acum, nu numai că îmi validez inputul JSON, dar îmi construiesc și un tip Typescript pe măsură ce merg. Tipurile generice de mai sus asigură că rezultatul deduce tipurile din „transformările” tale.

În cazul în care transformarea aruncă o eroare (ceea ce este modul în care ai implementa validarea), îmi place să o înfășor cu o altă eroare care arată ce cheie a cauzat eroarea.

Utilizare

În exemplul tău, aș folosi asta astfel:

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

Acum value va fi tipizat, deoarece String și Boolean sunt ambele „transformatoare” în sensul că iau un input și returnează un output tipizat.

În plus, value va fi de fapt acel tip. Cu alte cuvinte, dacă name ar fi fost de fapt 123, acesta va fi transformat în "123" astfel încât să ai un șir valid. Acest lucru se datorează faptului că am folosit String la runtime, o funcție încorporată care acceptă intrări arbitrare și returnează un string.

Poți vedea acest lucru funcționând aici. Încearcă următoarele lucruri pentru a te convinge:

  • Plasează cursorul peste definiția const value pentru a vedea că pop-over-ul afișează tipul corect.
  • Încercați să schimbați "Bob" în 123 și să rulați din nou exemplul. În consola dvs., veți vedea că numele a fost convertit corespunzător în șirul "123".