Come analizzare la stringa JSON in Typescript


99

C'è un modo per analizzare le stringhe come JSON in Typescript.
Esempio: in JS, possiamo usare JSON.parse(). Esiste una funzione simile in Typescript?

Ho una stringa oggetto JSON come segue:

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

1
Sulla sua homepage, si dice che "TypeScript è un superset tipizzato di JavaScript che si compila in JavaScript normale". La funzione JSON.parse () dovrebbe essere utilizzabile normalmente.
sigalor

1
Sto utilizzando l'editor di testo Atom e quando eseguo un JSON.parse, ottengo l'errore: L'argomento di tipo "{}" non è assegnabile al parametro di tipo "stringa"
ssd20072

21
Questa è una domanda molto semplice, e potrebbe sembrare banale per alcuni, ma è comunque una domanda valida, e un equivalente non può essere trovato in SO (non l'ho fatto) quindi non c'è un vero motivo per non mantenere la domanda correndo, e secondo me non dovrebbe essere votato anche lui.
Nitzan Tomer

2
@SanketDeshpande Quando usi JSON.parseottieni un oggetto come risultato e non un string(vedi la mia risposta per ulteriori informazioni). Se vuoi trasformare un oggetto in una stringa, devi JSON.stringifyinvece usare .
Nitzan Tomer

2
In realtà non è una domanda semplice per 2 motivi. In primo luogo, JSON.parse () non restituisce lo stesso tipo di oggetto: corrisponderà ad alcune delle interfacce ma nulla di intelligente, come le funzioni di accesso, non sarà presente. Inoltre, sicuramente vogliamo che SO sia dove le persone vanno quando cercano cose su Google?
specie Sconosciuta

Risposte:


165

Il dattiloscritto è (un superset di) javascript, quindi lo usi JSON.parsecome faresti in javascript:

let obj = JSON.parse(jsonString);

Solo che in dattiloscritto puoi avere un tipo per l'oggetto risultante:

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

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

( codice in playground )


9
come convalidare che l'input sia valido (controllo del tipo, uno degli scopi del dattiloscritto)? la sostituzione dell'input '{ "myString": "string", "myNumber": 4 }'con '{ "myString": "string", "myNumberBAD": 4 }'non fallirà e obj.myNumber restituirà undefined.
David Portabella

2
@DavidPortabella Non è possibile eseguire il controllo del tipo sul contenuto di una stringa. È un problema di runtime e il controllo del tipo è per il momento della compilazione
Nitzan Tomer,

2
ok. come posso verificare che un oggetto dattiloscritto soddisfi la sua interfaccia in fase di esecuzione? vale a dire che myNumber non è undefined in questo esempio. per esempio, in Scala Play, useresti Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson come puoi fare lo stesso in dattiloscritto (forse esiste una libreria esterna per farlo?)?
David Portabella

1
@DavidPortabella Non c'è modo di farlo, non facilmente, perché in fase di esecuzione MyObjnon esiste. Ci sono molti altri thread in SO su questo argomento, ad esempio: Controlla se un oggetto implementa un'interfaccia in fase di esecuzione con TypeScript
Nitzan Tomer

7
ok grazie. ogni giorno sono più convinto dell'uso di scalajs.
David Portabella,

5

A prova di tipo JSON.parse

Puoi continuare a utilizzare JSON.parse, poiché TS è un superset JS. C'è ancora un problema: i JSON.parseresiany , che mina la sicurezza dei tipi. Ecco due opzioni per i tipi più forti:

1. Tipo di guardie definito dall'utente ( parco giochi )

Le protezioni dei tipi personalizzate sono la soluzione più semplice e spesso sufficiente per la convalida dei dati esterni:

// 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
function isMyType(o: any): o is MyType {
  return "name" in o && "description" in o
}

Un JSON.parsewrapper può quindi prendere una protezione del tipo come input e restituire il valore digitato analizzato:

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 }
Esempio di utilizzo:
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`
}

safeJsonParsepotrebbe essere esteso per fallire velocemente o try / catchJSON.parse errori.

2. Biblioteche esterne

Scrivere manualmente le funzioni di protezione dei tipi diventa complicato, se è necessario convalidare molti valori diversi. Ci sono biblioteche per aiutare con questa attività - esempi (nessun elenco completo):

Maggiori informazioni


4

Se desideri che il tuo JSON abbia un tipo di Typescript convalidato, dovrai eseguire tu stesso il lavoro di convalida. Questa non è una novità. In semplice Javascript, dovresti fare lo stesso.

Validazione

Mi piace esprimere la mia logica di convalida come un insieme di "trasformazioni". Definisco una Descriptorcome mappa delle trasformazioni:

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

Quindi posso creare una funzione che applicherà queste trasformazioni a input arbitrari:

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

Ora, non solo sto convalidando il mio input JSON, ma sto creando un tipo Typescript mentre procedo. I tipi generici di cui sopra assicurano che il risultato deduca i tipi dalle tue "trasformazioni".

Nel caso in cui la trasformazione generi un errore (che è il modo in cui implementeresti la convalida), mi piace includerlo con un altro errore che mostra quale chiave ha causato l'errore.

Utilizzo

Nel tuo esempio, lo userei come segue:

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

Ora valueverrà digitato, poiché Stringe Booleansono entrambi "trasformatori" nel senso che prendono l'input e restituiscono un'uscita digitata.

Inoltre, valuesarà effettivamente quel tipo. In altre parole, se namefosse effettivamente 123, verrà trasformato in in "123"modo da avere una stringa valida. Questo perché abbiamo utilizzato Stringin fase di esecuzione, una funzione incorporata che accetta input arbitrari e restituisce un filestring .

Puoi vederlo funzionare qui . Prova le seguenti cose per convincerti:

  • Passa il mouse sulla const valuedefinizione per vedere che il pop-over mostra il tipo corretto.
  • Provare a cambiare "Bob"per 123e ri-eseguire l'esempio. Nella tua console, vedrai che il nome è stato correttamente convertito nella stringa "123".

hai dato un esempio, "se nameerano in realtà 123, si sarà trasformato a "123"Questo sembra essere errato mio.. valueè tornato {name: 123..non {name:"123"..quando copio incollare tutto il codice esattamente e fare che il cambiamento.
Joncom

Strano, per me funziona. Provalo qui: typescriptlang.org/play/index.html (usando al 123posto di "Bob").
chowey

Non penso che sia necessario definire un Transformedtipo. Puoi semplicemente usare Object. type Descriptor<T extends Object> = { ... };
lovasoa

Grazie @lovasoa, hai ragione. Il Transformedtipo è totalmente inutile. Ho aggiornato la risposta di conseguenza.
chowey

1

Puoi inoltre usare librerie che eseguono la convalida del tipo del tuo json, come Sparkson . Ti consentono di definire una classe TypeScript, a cui vorresti analizzare la tua risposta, nel tuo caso potrebbe essere:

import { Field } from "sparkson";
class Response {
   constructor(
      @Field("name") public name: string,
      @Field("error") public error: boolean
   ) {}
}

La libreria convaliderà se i campi obbligatori sono presenti nel payload JSON e se i loro tipi sono corretti. Può anche fare un sacco di convalide e conversioni.


Dovresti menzionare che sei il principale contributore della libreria di cui sopra.
ford04

1

C'è una grande libreria per esso ts-json-object

Nel tuo caso dovresti eseguire il codice seguente:

import {JSONObject, required} from 'ts-json-object'

class Response extends JSONObject {
    @required
    name: string;

    @required
    error: boolean;
}

let resp = new Response({"name": "Bob", "error": false});

Questa libreria convaliderà il json prima dell'analisi


0

JSON.parse è disponibile in TypeScript, quindi puoi semplicemente usarlo:

JSON.parse('{"name": "Bob", "error": false}') // Returns a value of type 'any'

Tuttavia, spesso vorrai analizzare un oggetto JSON assicurandoti che corrisponda a un certo tipo, piuttosto che trattare con un valore di tipo any. In tal caso, puoi definire una funzione come la seguente:

function parse_json<TargetType extends Object>(
  json: string,
  type_definitions: { [Key in keyof TargetType]: (raw_value: any) => TargetType[Key] }
): TargetType {
  const raw = JSON.parse(json); 
  const result: any = {};
  for (const key in type_definitions) result[key] = type_definitions[key](raw[key]);
  return result;
}

Questa funzione accetta una stringa JSON e un oggetto contenente singole funzioni che caricano ogni campo dell'oggetto che stai creando. Puoi usarlo in questo modo:

const value = parse_json(
  '{"name": "Bob", "error": false}',
  { name: String, error: Boolean, }
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.