So analysieren Sie JSON-Zeichenfolgen in Typescript

97

Gibt es eine Möglichkeit, Zeichenfolgen als JSON in Typescript zu analysieren?
Beispiel: In JS können wir verwenden JSON.parse(). Gibt es eine ähnliche Funktion in Typescript?

Ich habe eine JSON-Objektzeichenfolge wie folgt:

{"name": "Bob", "error": false}
ssd20072
quelle
1
Auf seiner Homepage heißt es: "TypeScript ist eine typisierte Obermenge von JavaScript, die zu einfachem JavaScript kompiliert wird." Die Funktion JSON.parse () sollte wie gewohnt verwendet werden können.
Sigalor
1
Ich verwende den Atom-Texteditor und wenn ich eine JSON.parse mache, erhalte ich die Fehlermeldung: Das Argument vom Typ '{}' kann nicht dem Parameter vom Typ 'string'
zugewiesen werden
21
Dies ist eine sehr grundlegende Frage, die einigen vielleicht trivial erscheint, aber dennoch eine gültige Frage ist, und ein Äquivalent kann in SO nicht gefunden werden (habe ich nicht), sodass es keinen wirklichen Grund gibt, die Frage nicht beizubehalten Laufen, und meiner Meinung nach sollte nicht auch nach unten gewählt werden.
Nitzan Tomer
2
@SanketDeshpande Wenn Sie verwenden, erhalten JSON.parseSie ein Objekt als Ergebnis und nicht ein string(siehe meine Antwort für mehr). Wenn Sie ein Objekt in eine Zeichenfolge verwandeln möchten, müssen Sie JSON.stringifystattdessen verwenden.
Nitzan Tomer
2
Eigentlich ist es aus zwei Gründen keine einfache Frage. Erstens gibt JSON.parse () nicht dieselbe Art von Objekt zurück - es stimmt mit einigen der Schnittstellen überein, aber alles Intelligente, wie z. B. Accessoren, ist nicht vorhanden. Darüber hinaus möchten wir sicher, dass SO dort ist, wo die Leute hingehen, wenn sie Sachen googeln?
Gburton

Antworten:

164

Typoskript ist (eine Obermenge von) Javascript, also verwenden Sie es einfach JSON.parsewie in Javascript:

let obj = JSON.parse(jsonString);

Nur dass Sie im Typoskript einen Typ für das resultierende Objekt haben können:

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

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

( Code auf dem Spielplatz )

Nitzan Tomer
quelle
9
Wie kann überprüft werden, ob die Eingabe gültig ist (Typprüfung, einer der Zwecke von Typoskript)? Das Ersetzen der Eingabe '{ "myString": "string", "myNumber": 4 }'durch '{ "myString": "string", "myNumberBAD": 4 }'schlägt nicht fehl und obj.myNumber gibt undefiniert zurück.
David Portabella
2
@DavidPortabella Der Inhalt einer Zeichenfolge kann nicht typüberprüft werden. Es ist ein Laufzeitproblem, und die Typprüfung ist für die Kompilierungszeit
Nitzan Tomer
2
OK. Wie kann ich überprüfen, ob ein Typoskript-Objekt zur Laufzeit seine Schnittstelle erfüllt? Das heißt, dass myNumber in diesem Beispiel nicht undefiniert ist. In Scala Play würden Sie beispielsweise verwenden Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson Wie können Sie dasselbe in Typoskript tun (möglicherweise gibt es eine externe Bibliothek, um dies zu tun?)?
David Portabella
1
@DavidPortabella Das ist nicht einfach, weil es zur Laufzeit MyObjnicht existiert. Es gibt viele andere Threads in SO zu diesem Thema, zum Beispiel: Überprüfen Sie, ob ein Objekt zur Laufzeit eine Schnittstelle mit TypeScript implementiert
Nitzan Tomer
7
OK danke. Jeden Tag bin ich mehr davon überzeugt, Scalajs zu verwenden.
David Portabella
4

Wenn Ihr JSON einen validierten Typescript-Typ haben soll, müssen Sie diese Validierungsarbeit selbst ausführen. Das ist nichts Neues. In einfachem Javascript müssten Sie dasselbe tun.

Validierung

Ich möchte meine Validierungslogik als eine Reihe von "Transformationen" ausdrücken. Ich definiere a Descriptorals Karte von Transformationen:

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

Dann kann ich eine Funktion erstellen, die diese Transformationen auf beliebige Eingaben anwendet:

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

Jetzt validiere ich nicht nur meine JSON-Eingabe, sondern baue auch unterwegs einen Typescript-Typ auf. Die oben genannten generischen Typen stellen sicher, dass das Ergebnis die Typen aus Ihren "Transformationen" ableitet.

Falls die Transformation einen Fehler auslöst (so würden Sie die Validierung implementieren), möchte ich sie mit einem weiteren Fehler umschließen, der anzeigt, welcher Schlüssel den Fehler verursacht hat.

Verwendung

In Ihrem Beispiel würde ich dies wie folgt verwenden:

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

Jetzt valuewird getippt, da Stringund Booleanbeide "Transformatoren" in dem Sinne sind, dass sie Eingaben nehmen und eine getippte Ausgabe zurückgeben.

Darüber hinaus ist der valueWille tatsächlich , dass Art. Mit anderen Worten, wenn dies nametatsächlich der 123Fall wäre , wird es so transformiert, "123"dass Sie eine gültige Zeichenfolge haben. Dies liegt daran, dass wir zur StringLaufzeit eine integrierte Funktion verwendet haben, die beliebige Eingaben akzeptiert und a zurückgibt string.

Sie können dies hier sehen . Versuchen Sie Folgendes, um sich selbst zu überzeugen:

  • Bewegen Sie den Mauszeiger über die const valueDefinition, um anzuzeigen, dass im Popup der richtige Typ angezeigt wird.
  • Versuchen Sie , "Bob"auf 123und die Probe erneut aus. In Ihrer Konsole sehen Sie, dass der Name ordnungsgemäß in die Zeichenfolge konvertiert wurde "123".
Chowey
quelle
Sie gaben ein Beispiel: "Wenn dies nametatsächlich der 123Fall wäre , wird es in umgewandelt "123". Dies scheint falsch zu sein. Meines valuekommt {name: 123..nicht zurück, {name:"123"..wenn ich Ihren gesamten Code kopiere, genau
einfüge
Seltsam, es funktioniert bei mir. Versuchen Sie es hier: typescriptlang.org/play/index.html (mit 123anstelle von "Bob").
Chowey
Ich glaube nicht, dass Sie einen TransformedTyp definieren müssen . Sie können einfach verwenden Object. type Descriptor<T extends Object> = { ... };
Lovasoa
Danke @lovasoa, du bist richtig. Der TransformedTyp ist völlig unnötig. Ich habe die Antwort entsprechend aktualisiert.
Chowey
3

Typensicher JSON.parse

Sie können weiterhin verwenden JSON.parse, da TS eine JS-Obermenge ist. Es gibt immer noch ein Problem: JSON.parseRückgabe any, die die Typensicherheit untergräbt. Hier sind zwei Optionen für stärkere Typen:

1. Benutzerdefinierte Typwächter ( Spielplatz )

Type Guards sind die einfachste Lösung und oft ausreichend für die externe Datenvalidierung:

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

Ein JSON.parseWrapper nimmt dann einen Typschutz als Eingabe und gibt den analysierten, typisierten Wert zurück:

type ParseResult<T> =
  | { parsed: T; hasError: false; error?: undefined }
  | { parsed?: undefined; hasError: true; error?: unknown }

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 }
}
Anwendungsbeispiel:
const json = "{ \"name\": \"Foo\", \"description\": \"Bar\" }"
const result = safeJsonParse(isMyType)(json) // result: ParseResult<MyType>
if (result.hasError) {
  console.log("error :/")  // error handling here
} else {
  console.log(result.parsed.description) // parsed has type `MyType`
}

safeJsonParsekann erweitert werden, um schnell zu versagen oder JSON.parseFehler zu versuchen / zu fangen .

2. Externe Bibliotheken

Das manuelle Schreiben von Typschutzfunktionen wird umständlich, wenn Sie viele verschiedene Werte validieren müssen. Es gibt einige Bibliotheken, die bei dieser Aufgabe helfen - Beispiele (keine umfassende Liste):

Weitere Infos

ford04
quelle
1

Sie können zusätzlich Bibliotheken verwenden, die eine Typüberprüfung Ihres JSON durchführen, z. B. Sparkson . Mit ihnen können Sie eine TypeScript-Klasse definieren, auf die Sie Ihre Antwort analysieren möchten. In Ihrem Fall könnte dies sein:

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

Die Bibliothek überprüft, ob die erforderlichen Felder in der JSON-Nutzlast vorhanden sind und ob ihre Typen korrekt sind. Es kann auch eine Reihe von Validierungen und Konvertierungen durchführen.

jfu
quelle
Sie sollten erwähnen, dass Sie der Hauptverantwortliche für die oben genannte Bibliothek sind.
ford04
1

Es gibt eine großartige Bibliothek für das ts-json-Objekt

In Ihrem Fall müssten Sie den folgenden Code ausführen:

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

Diese Bibliothek überprüft den JSON vor dem Parsen

Elisha Sterngold
quelle
0

JSON.parse ist in TypeScript verfügbar, Sie können es also einfach verwenden:

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

Sie möchten jedoch häufig ein JSON-Objekt analysieren, während Sie sicherstellen, dass es einem bestimmten Typ entspricht, anstatt sich mit einem Wert vom Typ zu befassen any. In diesem Fall können Sie eine Funktion wie die folgende definieren:

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

Diese Funktion verwendet eine JSON-Zeichenfolge und ein Objekt, das einzelne Funktionen enthält, die jedes Feld des von Ihnen erstellten Objekts laden. Sie können es so verwenden:

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