Cómo analizar una cadena JSON en Typecript

111

¿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}
ssd20072
fuente
1
En su página de inicio, dice que "TypeScript es un superconjunto escrito de JavaScript que se compila en JavaScript simple". La función JSON.parse () debería poder usarse normalmente.
sigalor
1
Estoy usando el editor de texto Atom y cuando hago un JSON.parse, aparece el error: El argumento de tipo '{}' no se puede asignar al parámetro de tipo 'cadena'
ssd20072
23
Esta es una pregunta muy básica, y puede parecer trivial para algunos, pero es una pregunta válida de todos modos, y no se puede encontrar un equivalente en SO (no lo he hecho), por lo que no hay una razón real por la que no mantener la pregunta corriendo, y en mi opinión no debería ser rechazado también.
Nitzan Tomer
2
@SanketDeshpande Cuando lo usa 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.stringifyen su lugar.
Nitzan Tomer
2
En realidad, no es una pregunta sencilla por dos razones. En primer lugar, JSON.parse () no devuelve el mismo tipo de objeto; coincidirá con parte de la interfaz, pero no habrá nada inteligente, como los accesores. Además, seguramente queremos que SO esté donde la gente va cuando busca cosas en Google.
especie desconocida

Respuestas:

184

Typecript es (un superconjunto de) javascript, por lo que solo usa JSON.parsecomo 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 )

Nitzan Tomer
fuente
10
¿Cómo validar que la entrada es válida (verificación de tipo, uno de los propósitos del mecanografiado)? reemplazar la entrada '{ "myString": "string", "myNumber": 4 }'por '{ "myString": "string", "myNumberBAD": 4 }'no fallará, y obj.myNumber devolverá undefined.
David Portabella
3
@DavidPortabella No puede tener verificación de tipo en el contenido de una cadena. Es una cuestión de tiempo de ejecución, y la verificación de tipos es para tiempo de compilación
Nitzan Tomer
2
Okay. ¿Cómo puedo validar que un obj mecanografiado satisfaga su interfaz en tiempo de ejecución? es decir, myNumber no está indefinido en este ejemplo. por ejemplo, en Scala Play, usaría 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?)?
David Portabella
1
@DavidPortabella No hay forma de hacer eso, no es fácil, porque en tiempo de ejecución MyObjno 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
Nitzan Tomer
7
OK gracias. cada día estoy más convencido de usar scalajs.
David Portabella
8

Tipo seguro JSON.parse

Puede seguir utilizándolo JSON.parse, ya que TS es un superconjunto JS. Todavía queda un problema: las JSON.parsedevoluciones any, 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.parsecontenedor 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`
}

safeJsonParsepodría extenderse para fallar rápidamente o intentar / detectar JSON.parseerrores.

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):

Más infos

ford04
fuente
4

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 Descriptorcomo 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 valuese escribirá, ya que Stringy Booleanambos son "transformadores" en el sentido de que toman entrada y devuelven una salida escrita.

Por otra parte, la valuevoluntad ser en realidad ese tipo. En otras palabras, si namefuera realmente 123, se transformará a "123"para que tenga una cadena válida. Esto se debe a que usamos Stringen 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:

  • Desplácese sobre la const valuedefinición para ver que la ventana emergente muestra el tipo correcto.
  • Intente cambiar "Bob"a 123y volver a ejecutar el ejemplo. En su consola, verá que el nombre se ha convertido correctamente a la cadena "123".
chowey
fuente
dio un ejemplo, "si namefuera en realidad 123, se transformará en "123". Esto parece ser incorrecto. Mi no valueregresará cuando copie, pegue todo su código exactamente y haga ese cambio.{name: 123..{name:"123"..
Joncom
Extraño, me funciona. Pruébelo aquí: typescriptlang.org/play/index.html (usando en 123lugar de "Bob").
chowey
No creo que sea necesario definir un Transformedtipo. Puedes usar Object. type Descriptor<T extends Object> = { ... };
lovasoa
Gracias @lovasoa, tienes razón. El Transformedtipo es totalmente innecesario. Actualicé la respuesta en consecuencia.
chowey
Si realmente desea validar que el objeto JSON tiene los tipos correctos, no querrá 123que se convierta automáticamente en una cadena "123", ya que es un número en el objeto JSON.
xuiqzy
1

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.

jfu
fuente
1
Debe mencionar que es el principal contribuyente de la biblioteca anterior.
Ford04
1

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

Eliseo Sterngold
fuente
0

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, }
);
lovasoa
fuente
0

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, Objecty Mathtambién están disponibles en el TS. Por lo tanto, podemos usar el JSON.parsemé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 anyque 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
}
Willem van der Veen
fuente