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}
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}
JSON.parse
ottieni 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.stringify
invece usare .
Risposte:
Il dattiloscritto è (un superset di) javascript, quindi lo usi JSON.parse
come 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);
'{ "myString": "string", "myNumber": 4 }'
con '{ "myString": "string", "myNumberBAD": 4 }'
non fallirà e obj.myNumber restituirà undefined.
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?)?
MyObj
non 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
JSON.parse
Puoi continuare a utilizzare JSON.parse
, poiché TS è un superset JS. C'è ancora un problema: i JSON.parse
resiany
, che mina la sicurezza dei tipi. Ecco due opzioni per i tipi più forti:
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.parse
wrapper 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`
}
safeJsonParse
potrebbe essere esteso per fallire velocemente o try / catchJSON.parse
errori.
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):
io-ts
: rel. popolare (3.2k stelle attualmente), fp-ts
dipendenza tra pari, stile di programmazione funzionalezod
: abbastanza nuovo (repo: 2020-03-07), si sforza di essere più procedurale / orientato agli oggetti rispetto aio-ts
typescript-is
: Trasformatore TS per API del compilatore, wrapper aggiuntivo come ttypescript necessariotypescript-json-schema
/ ajv
: Crea uno schema JSON dai tipi e convalidalo conajv
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.
Mi piace esprimere la mia logica di convalida come un insieme di "trasformazioni". Definisco una Descriptor
come 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.
Nel tuo esempio, lo userei come segue:
const value = pick(JSON.parse('{"name": "Bob", "error": false}'), {
name: String,
error: Boolean,
});
Ora value
verrà digitato, poiché String
e Boolean
sono entrambi "trasformatori" nel senso che prendono l'input e restituiscono un'uscita digitata.
Inoltre, value
sarà effettivamente quel tipo. In altre parole, se name
fosse effettivamente 123
, verrà trasformato in in "123"
modo da avere una stringa valida. Questo perché abbiamo utilizzato String
in fase di esecuzione, una funzione incorporata che accetta input arbitrari e restituisce un filestring
.
Puoi vederlo funzionare qui . Prova le seguenti cose per convincerti:
const value
definizione per vedere che il pop-over mostra il tipo corretto."Bob"
per 123
e ri-eseguire l'esempio. Nella tua console, vedrai che il nome è stato correttamente convertito nella stringa "123"
.name
erano 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.
123
posto di "Bob"
).
Transformed
tipo. Puoi semplicemente usare Object
. type Descriptor<T extends Object> = { ... };
Transformed
tipo è totalmente inutile. Ho aggiornato la risposta di conseguenza.
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.
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
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, }
);