Comment analyser la chaîne JSON dans Typescript

99

Existe-t-il un moyen d'analyser les chaînes en tant que JSON dans Typescript.
Exemple: Dans JS, nous pouvons utiliser JSON.parse(). Existe-t-il une fonction similaire dans Typescript?

J'ai une chaîne d'objet JSON comme suit:

{"name": "Bob", "error": false}
ssd20072
la source
1
Sur sa page d'accueil, il est dit que "TypeScript est un sur-ensemble typé de JavaScript qui se compile en JavaScript brut". La fonction JSON.parse () doit être utilisable comme d'habitude.
sigalor
1
J'utilise l'éditeur de texte Atom et quand je fais un JSON.parse, j'obtiens l'erreur: L'argument de type '{}' n'est pas assignable au paramètre de type 'string'
ssd20072
21
C'est une question très basique, et cela peut sembler trivial pour certains, mais c'est une question valide quand même, et un équivalent ne peut pas être trouvé dans SO (je ne l'ai pas) donc il n'y a pas de vraie raison de ne pas garder la question et, à mon avis, ne devrait pas non plus être voté contre.
Nitzan Tomer
2
@SanketDeshpande Lorsque vous utilisez, JSON.parsevous obtenez un objet en conséquence et non un string(voir ma réponse pour plus). Si vous souhaitez transformer un objet en chaîne, vous devez utiliser à la JSON.stringifyplace.
Nitzan Tomer
2
En fait, ce n'est pas une question simple pour 2 raisons. Premièrement, JSON.parse () ne renvoie pas le même type d'objet - il correspondra à une partie de l'interface mais tout ce qui est intelligent, comme les accesseurs, ne sera pas présent. En outre, nous voulons sûrement que SO soit là où les gens vont quand ils Google trucs?
speciesUnknown

Réponses:

165

Typescript est (un sur-ensemble de) javascript, vous utilisez donc simplement JSON.parsecomme vous le feriez en javascript:

let obj = JSON.parse(jsonString);

Seulement que dans dactylographié, vous pouvez avoir un type pour l'objet résultant:

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

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

( code dans la cour de récréation )

Nitzan Tomer
la source
9
comment valider que l'entrée est valide (vérification de type, l'un des objectifs de la dactylographie)? le remplacement de l'entrée '{ "myString": "string", "myNumber": 4 }'par '{ "myString": "string", "myNumberBAD": 4 }'n'échouera pas et obj.myNumber retournera undefined.
David Portabella
2
@DavidPortabella Vous ne pouvez pas avoir de vérification de type sur le contenu d'une chaîne. C'est un problème d'exécution, et la vérification de type est pour la compilation
Nitzan Tomer
2
D'accord. comment puis-je valider qu'un objet dactylographié satisfait son interface au moment de l'exécution? c'est-à-dire que myNumber n'est pas indéfini dans cet exemple. par exemple, dans Scala Play, vous utiliseriez Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson comment pouvez-vous faire la même chose en tapuscrit (peut-être existe-t-il une bibliothèque externe pour le faire?)?
David Portabella
1
@DavidPortabella Il n'y a aucun moyen de faire ça, pas facilement, car au moment de l'exécution MyObjn'existe pas. Il existe de nombreux autres threads dans SO sur ce sujet, par exemple: Vérifier si un objet implémente une interface au moment de l'exécution avec TypeScript
Nitzan Tomer
7
OK merci. tous les jours, je suis plus convaincu de l'utilisation de scalajs.
David Portabella le
5

Type-safe JSON.parse

Vous pouvez continuer à utiliser JSON.parse, car TS est un sur-ensemble JS. Il reste un problème: les JSON.parseretours any, qui sapent la sécurité des types. Voici deux options pour les types plus forts:

1. Protections de type défini par l'utilisateur ( terrain de jeu )

Les protections de type personnalisées sont la solution la plus simple et souvent suffisante pour la validation externe des données:

// 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 peut alors prendre un type guard comme entrée et renvoyer la valeur typée analysée:

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 }
Exemple d'utilisation:
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`
}

safeJsonParsepeut être étendu pour échouer rapidement ou essayer / attraper des JSON.parseerreurs.

2. Bibliothèques externes

L'écriture manuelle des fonctions de protection de type devient fastidieuse, si vous devez valider de nombreuses valeurs différentes. Il existe des bibliothèques pour vous aider dans cette tâche - exemples (pas de liste complète):

Plus d'infos

ford04
la source
4

Si vous souhaitez que votre JSON ait un type Typecript validé, vous devrez effectuer vous-même ce travail de validation. Cela n’a rien de nouveau. En Javascript simple, vous devrez faire de même.

Validation

J'aime exprimer ma logique de validation comme un ensemble de «transformations». Je définis a Descriptorcomme une carte de transformations:

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

Ensuite, je peux créer une fonction qui appliquera ces transformations à une entrée arbitraire:

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

Maintenant, non seulement je valide mon entrée JSON, mais je construis un type Typecript au fur et à mesure. Les types génériques ci-dessus garantissent que le résultat infère les types à partir de vos "transformations".

Au cas où la transformation lèverait une erreur (c'est ainsi que vous implémenteriez la validation), j'aime l'envelopper avec une autre erreur indiquant quelle clé a causé l'erreur.

Usage

Dans votre exemple, j'utiliserais ceci comme suit:

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

Maintenant valuesera tapé, puisque Stringet Booleansont tous deux des "transformateurs" dans le sens où ils prennent une entrée et retournent une sortie typée.

De plus, le valuesera réellement de ce type. En d'autres termes, si namec'était réellement 123, il sera transformé en "123"afin que vous ayez une chaîne valide. C'est parce que nous avons utilisé Stringau moment de l'exécution, une fonction intégrée qui accepte une entrée arbitraire et renvoie unstring .

Vous pouvez voir cela fonctionner ici . Essayez les choses suivantes pour vous convaincre:

  • Survolez la const valuedéfinition pour voir que la fenêtre contextuelle affiche le type correct.
  • Essayez de changer "Bob"à 123et re-exécuter l'exemple. Dans votre console, vous verrez que le nom a été correctement converti en chaîne "123".
Chowey
la source
vous avez donné un exemple, "si namec'était effectivement le cas 123, il sera transformé en "123". Cela semble être incorrect. Je ne valuereviens {name: 123..pas {name:"123"..quand je copie-collez tout votre code exactement et que
j'effectue
Bizarre, ça marche pour moi. Essayez-le ici: typescriptlang.org/play/index.html (en utilisant 123au lieu de "Bob").
chowey
Je ne pense pas que vous ayez besoin de définir un Transformedtype. Vous pouvez simplement utiliser Object. type Descriptor<T extends Object> = { ... };
lovasoa
Merci @lovasoa, vous avez raison. Le Transformedtype est totalement inutile. J'ai mis à jour la réponse en conséquence.
chowey le
1

Vous pouvez en outre utiliser des bibliothèques qui effectuent la validation de type de votre json, telles que Sparkson . Ils vous permettent de définir une classe TypeScript, à laquelle vous souhaitez analyser votre réponse, dans votre cas cela pourrait être:

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

La bibliothèque validera si les champs obligatoires sont présents dans la charge utile JSON et si leurs types sont corrects. Il peut également effectuer un tas de validations et de conversions.

jfu
la source
Vous devez mentionner que vous êtes le principal contributeur de la bibliothèque ci-dessus.
ford04
1

Il existe une excellente bibliothèque pour cela ts-json-object

Dans votre cas, vous devrez exécuter le code suivant:

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

Cette bibliothèque validera le json avant l'analyse

Elisha Sterngold
la source
0

JSON.parse est disponible dans TypeScript, vous pouvez donc simplement l'utiliser:

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

Cependant, vous souhaiterez souvent analyser un objet JSON tout en vous assurant qu'il correspond à un certain type, plutôt que de traiter une valeur de type any. Dans ce cas, vous pouvez définir une fonction telle que la suivante:

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

Cette fonction prend une chaîne JSON et un objet contenant des fonctions individuelles qui chargent chaque champ de l'objet que vous créez. Vous pouvez l'utiliser comme ceci:

const value = parse_json(
  '{"name": "Bob", "error": false}',
  { name: String, error: Boolean, }
);
Lovasoa
la source