¿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}
¿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}
JSON.parse
, obtiene un objeto como resultado y no un string
(vea mi respuesta para obtener más información). Si desea convertir un objeto en una cadena, debe usar JSON.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);
'{ "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 TypeScript
JSON.parse
Puede seguir utilizándolo JSON.parse
, ya que TS es un superconjunto JS. Todavía queda un problema: las JSON.parse
devoluciones any
, que atenta contra la seguridad de los tipos. Aquí hay dos opciones para tipos más fuertes:
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:
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 }
Ejemplo de uso:
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 / detectar JSON.parse
errores.
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
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.
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.
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 que String
y Boolean
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, si name
fuera realmente 123
, se transformará a "123"
para que tenga una cadena válida. Esto se debe a que usamos String
en tiempo de ejecución, una función incorporada que acepta entradas arbitrarias y devuelve un string
.
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"
a 123
y volver a ejecutar el ejemplo. En su consola, verá que el nombre se ha convertido correctamente a la cadena "123"
.name
fuera en realidad 123
, se transformará en "123"
. Esto parece ser incorrecto. Mi no value
regresará cuando copie, pegue todo su código exactamente y haga ese cambio.{name: 123..
{name:"123"..
123
lugar de "Bob"
).
Transformed
tipo. Puedes usar Object
. 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.
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
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, }
);
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
y Math
también están disponibles en el TS. Por lo tanto, podemos usar el JSON.parse
método para analizar la cadena JSON.
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
}