¿Hay alguna forma de analizar cadenas como JSON en TypeScript?
Ejemplo: en JS, podemos usar JSON.parse()
. ¿Existe una función similar en TypeScript?
Tengo una cadena de objeto JSON de la siguiente manera:
{"name": "Bob", "error": false}
javascript
json
string
typescript
ssd20072
fuente
fuente
JSON.parse
, obtiene un objeto como resultado y no unstring
(vea mi respuesta para obtener más información). Si desea convertir un objeto en una cadena, debe usarJSON.stringify
en su lugar.Respuestas:
Typecript es (un superconjunto de) javascript, por lo que solo usa
JSON.parse
como lo haría en javascript:let obj = JSON.parse(jsonString);
Solo que en mecanografiado puede tener un tipo para el objeto resultante:
interface MyObj { myString: string; myNumber: number; } let obj: MyObj = JSON.parse('{ "myString": "string", "myNumber": 4 }'); console.log(obj.myString); console.log(obj.myNumber);
( código en el patio de recreo )
fuente
'{ "myString": "string", "myNumber": 4 }'
por'{ "myString": "string", "myNumberBAD": 4 }'
no fallará, y obj.myNumber devolverá undefined.Json.parse(text).validate[MyObj]
. playframework.com/documentation/2.6.x/ScalaJson ¿cómo puedes hacer lo mismo en mecanografiado (tal vez haya una biblioteca externa para hacerlo?)?MyObj
no existe. Hay muchos otros hilos en SO sobre este tema, por ejemplo: Compruebe si un objeto implementa una interfaz en tiempo de ejecución con TypeScriptTipo seguro
JSON.parse
Puede seguir utilizándolo
JSON.parse
, ya que TS es un superconjunto JS. Todavía queda un problema: lasJSON.parse
devolucionesany
, que atenta contra la seguridad de los tipos. Aquí hay dos opciones para tipos más fuertes:1. Guardias de tipo definido por el usuario ( patio de recreo )
Los protectores de tipo personalizados son la solución más simple y, a menudo, suficientes para la validación de datos externos:
// 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
contenedor puede tomar un tipo de protección como entrada y devolver el valor escrito y analizado:
Ejemplo de uso: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 }
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
podría extenderse para fallar rápidamente o intentar / detectarJSON.parse
errores.2. Bibliotecas externas
Escribir funciones de protección de tipo manualmente se vuelve engorroso si necesita validar muchos valores diferentes. Hay bibliotecas para ayudar con esta tarea; ejemplos (sin lista completa):
io-ts
: rel. popular (3.2k estrellas actualmente),fp-ts
dependencia de pares, estilo de programación funcionalzod
: bastante nuevo (repo: 2020-03-07), se esfuerza por ser más procedimental / orientado a objetos queio-ts
typescript-is
: TS transformador para la API del compilador, se necesita un contenedor adicional como ttypescripttypescript-json-schema
/ajv
: Cree un esquema JSON a partir de tipos y valídelo conajv
Más infos
fuente
Si desea que su JSON tenga un tipo de Typecript validado, deberá realizar ese trabajo de validación usted mismo. Esto no es nada nuevo. En Javascript simple, necesitaría hacer lo mismo.
Validación
Me gusta expresar mi lógica de validación como un conjunto de "transformaciones". Defino a
Descriptor
como un mapa de transformaciones:type Descriptor<T> = { [P in keyof T]: (v: any) => T[P]; };
Entonces puedo hacer una función que aplique estas transformaciones a una entrada arbitraria:
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; }
Ahora, no solo estoy validando mi entrada JSON, sino que estoy construyendo un tipo de Typecript sobre la marcha. Los tipos genéricos anteriores aseguran que el resultado infiera los tipos de sus "transformaciones".
En caso de que la transformación arroje un error (que es cómo implementaría la validación), me gusta envolverlo con otro error que muestre qué clave causó el error.
Uso
En su ejemplo, usaría esto de la siguiente manera:
const value = pick(JSON.parse('{"name": "Bob", "error": false}'), { name: String, error: Boolean, });
Ahora
value
se escribirá, ya queString
yBoolean
ambos son "transformadores" en el sentido de que toman entrada y devuelven una salida escrita.Por otra parte, la
value
voluntad ser en realidad ese tipo. En otras palabras, siname
fuera realmente123
, se transformará a"123"
para que tenga una cadena válida. Esto se debe a que usamosString
en tiempo de ejecución, una función incorporada que acepta entradas arbitrarias y devuelve unstring
.Puedes ver esto funcionando aquí . Prueba las siguientes cosas para convencerte:
const value
definición para ver que la ventana emergente muestra el tipo correcto."Bob"
a123
y volver a ejecutar el ejemplo. En su consola, verá que el nombre se ha convertido correctamente a la cadena"123"
.fuente
name
fuera en realidad123
, se transformará en"123"
. Esto parece ser incorrecto. Mi novalue
regresará cuando copie, pegue todo su código exactamente y haga ese cambio.{name: 123..
{name:"123"..
123
lugar de"Bob"
).Transformed
tipo. Puedes usarObject
.type Descriptor<T extends Object> = { ... };
Transformed
tipo es totalmente innecesario. Actualicé la respuesta en consecuencia.123
que se convierta automáticamente en una cadena"123"
, ya que es un número en el objeto JSON.Además, puede usar bibliotecas que realizan la validación de tipo de su json, como Sparkson . Le permiten definir una clase de TypeScript, a la que le gustaría analizar su respuesta, en su caso podría ser:
import { Field } from "sparkson"; class Response { constructor( @Field("name") public name: string, @Field("error") public error: boolean ) {} }
La biblioteca validará si los campos obligatorios están presentes en la carga útil JSON y si sus tipos son correctos. También puede hacer un montón de validaciones y conversiones.
fuente
Hay una gran biblioteca para ts-json-object
En su caso, necesitaría ejecutar el siguiente código:
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});
Esta biblioteca validará el json antes de analizar
fuente
JSON.parse
está disponible en TypeScript, por lo que puede usarlo:JSON.parse('{"name": "Bob", "error": false}') // Returns a value of type 'any'
Sin embargo, a menudo querrá analizar un objeto JSON mientras se asegura de que coincida con un cierto tipo, en lugar de tratar con un valor de tipo
any
. En ese caso, puede definir una función como la siguiente: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; }
Esta función toma una cadena JSON y un objeto que contiene funciones individuales que cargan cada campo del objeto que está creando. Puedes usarlo así:
const value = parse_json( '{"name": "Bob", "error": false}', { name: String, error: Boolean, } );
fuente
TS tiene un tiempo de ejecución de JavaScript
TypeScript tiene un tiempo de ejecución de JavaScript porque se compila en JS. Esto significa JS objetos que se construyen en el marco de la lengua como el
JSON
,Object
yMath
también están disponibles en el TS. Por lo tanto, podemos usar elJSON.parse
método para analizar la cadena JSON.Ejemplo:
const JSONStr = '{"name": "Bob", "error": false}' // The JSON object is part of the runtime const parsedObj = JSON.parse(JSONStr); console.log(parsedObj); // [LOG]: { // "name": "Bob", // "error": false // } // The Object object is also part of the runtime so we can use it in TS const objKeys = Object.keys(parsedObj); console.log(objKeys); // [LOG]: ["name", "error"]
Lo único ahora es que parsedObj es de tipo, lo
any
que generalmente es una mala práctica en TS. Podemos escribir el objeto si usamos guardias de tipo. Aquí hay un ejemplo:const JSONStr = '{"name": "Bob", "error": false}' const parsedObj = JSON.parse(JSONStr); interface nameErr { name: string; error: boolean; } function isNameErr(arg: any): arg is nameErr { if (typeof arg.name === 'string' && typeof arg.error === 'boolean') { return true; } else { return false; } } if (isNameErr(parsedObj)) { // Within this if statement parsedObj is type nameErr; parsedObj }
fuente