So analysieren Sie JSON-Strings in Typescript

Lesezeit: 13 Minuten

Benutzer-Avatar
SSD20072

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}

  • Auf seiner Homepage heißt es, dass “TypeScript eine typisierte Obermenge von JavaScript ist, die zu einfachem JavaScript kompiliert wird”. Die Funktion JSON.parse() sollte wie gewohnt verwendbar sein.

    – Sigal

    31. Juli 2016 um 22:22 Uhr

  • Ich verwende den Atom-Texteditor und wenn ich eine JSON.parse durchführe, erhalte ich den Fehler: Argument vom Typ „{}“ kann Parameter vom Typ „String“ nicht zugewiesen werden.

    – ssd20072

    31. Juli 2016 um 22:25 Uhr


  • Dies ist eine sehr grundlegende Frage, die manchen trivial erscheinen mag, aber es ist nichtsdestotrotz eine gültige Frage, und ein Äquivalent kann in SO nicht gefunden werden (ich habe es nicht), also gibt es keinen wirklichen Grund, die Frage nicht zu behalten läuft, und sollte meiner Meinung nach auch nicht heruntergestimmt werden.

    – Nitzan Tomer

    31. Juli 2016 um 22:36 Uhr


  • @SanketDeshpande Wenn Sie verwenden JSON.parse Sie erhalten als Ergebnis ein Objekt und nicht ein string (mehr dazu in meiner Antwort). Wenn Sie ein Objekt in eine Zeichenfolge umwandeln möchten, müssen Sie verwenden JSON.stringify stattdessen.

    – Nitzan Tomer

    31. Juli 2016 um 22:38 Uhr

  • Eigentlich ist es aus 2 Gründen keine einfache Frage. Erstens gibt JSON.parse() nicht die gleiche Art von Objekt zurück – es stimmt mit einem Teil der Schnittstelle überein, aber alles Intelligente, wie z. B. Accessoren, ist nicht vorhanden. Außerdem wollen wir doch sicher, dass SO der Ort ist, an den die Leute gehen, wenn sie Sachen googeln?

    – ArtUnbekannt

    26. Oktober 2018 um 13:03 Uhr

Typoskript ist (eine Obermenge von) Javascript, also verwenden Sie einfach JSON.parse wie Sie es in Javascript tun würden:

let obj = JSON.parse(jsonString);

Nur dass Sie in 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)

  • Wie kann man überprüfen, ob die Eingabe gültig ist (Typprüfung, einer der Zwecke von Typoskript)? Eingang ersetzen '{ "myString": "string", "myNumber": 4 }' durch '{ "myString": "string", "myNumberBAD": 4 }' schlägt nicht fehl, und obj.myNumber gibt undefiniert zurück.

    – David Portabella

    25. September 2017 um 13:00 Uhr

  • @DavidPortabella Sie können den Inhalt einer Zeichenfolge nicht typüberprüfen. Es ist ein Laufzeitproblem, und die Typprüfung dient der Kompilierzeit

    – Nitzan Tomer

    25. September 2017 um 13:56 Uhr

  • OK. Wie kann ich überprüfen, ob ein Typoskript-Objekt seine Schnittstelle zur Laufzeit erfüllt? das heißt, dass myNumber in diesem Beispiel nicht undefiniert ist. Zum Beispiel würden Sie in Scala Play verwenden Json.parse(text).validate[MyObj]. playframework.com/documentation/2.6.x/ScalaJson Wie können Sie dasselbe in Typoskript tun (vielleicht gibt es eine externe Bibliothek dafür?)?

    – David Portabella

    25. September 2017 um 18:02 Uhr

  • @DavidPortabella Es gibt keine Möglichkeit, das zu tun, nicht einfach, weil zur Laufzeit MyObj existiert nicht. Zu diesem Thema gibt es in SO viele weitere Threads, zum Beispiel: Mit TypeScript prüfen, ob ein Objekt zur Laufzeit eine Schnittstelle implementiert

    – Nitzan Tomer

    25. September 2017 um 18:15 Uhr

  • OK danke. Jeden Tag bin ich mehr davon überzeugt, Scalajs zu verwenden.

    – David Portabella

    27. September 2017 um 13:11 Uhr

Benutzer-Avatar
ford04

Typsicher JSON.parse

Sie können weiter verwenden JSON.parseda TypeScript ein ist Obermenge von JavaScript:

Das bedeutet, dass Sie jeden funktionierenden JavaScript-Code nehmen und ihn in eine TypeScript-Datei einfügen können, ohne sich Gedanken darüber machen zu müssen, wie er genau geschrieben ist.

Es bleibt ein Problem: JSON.parse kehrt zurück anywas die Typsicherheit untergräbt (nicht verwenden any).

Hier sind drei Lösungen für stärkere Typen, geordnet nach aufsteigender Komplexität:

1. Benutzerdefinierte Typwächter

Spielplatz

// 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 (extend to your needs)
function isMyType(o: any): o is MyType {
  return "name" in o && "description" in o
}

const json = '{ "name": "Foo", "description": "Bar" }';
const parsed = JSON.parse(json);
if (isMyType(parsed)) {
  // do something with now correctly typed object
  parsed.description
} else { 
// error handling; invalid JSON format 
}

isMyType heißt ein Typ Wache. Sein Vorteil ist, dass Sie ein vollständig typisiertes Objekt in der Wahrheit erhalten if Zweig.

2. Allgemein JSON.parse Verpackung

Spielplatz

Erstellen Sie einen generischen Wrapper um JSON.parsedie einen Typwächter als Eingabe verwendet und den geparsten, typisierten Wert oder das Fehlerergebnis zurückgibt:

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 }

Anwendungsbeispiel:

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 könnte erweitert werden scheitern schnell oder versuchen/fangen JSON.parse Fehler.

3. Externe Bibliotheken

Das manuelle Schreiben von Type Guard-Funktionen wird umständlich, wenn Sie viele verschiedene Werte validieren müssen. Es gibt Bibliotheken, die bei dieser Aufgabe helfen – Beispiele (keine vollständige Liste):


Weitere Infos

Benutzer-Avatar
Chowey

Wenn Sie möchten, dass Ihr JSON einen validierten Typescript-Typ hat, müssen Sie diese Validierungsarbeit selbst durchführen. Das ist nichts Neues. In einfachem Javascript müssten Sie dasselbe tun.

Validierung

Ich drücke meine Validierungslogik gerne als eine Reihe von “Transformationen” aus. Ich definiere a Descriptor als 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 währenddessen einen Typescript-Typ auf. Die obigen 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 zeigt, welcher Schlüssel den Fehler verursacht hat.

Verwendungszweck

In deinem Beispiel würde ich das wie folgt verwenden:

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

Jetzt value wird getippt, da String und Boolean sind beide “Transformatoren” in dem Sinne, dass sie Eingaben entgegennehmen und eine typisierte Ausgabe zurückgeben.

Außerdem die value Wille eigentlich sein dieser Typ. Mit anderen Worten, wenn name waren eigentlich 123es wird umgewandelt in "123" damit Sie einen gültigen String haben. Das liegt daran, dass wir verwendet haben String zur Laufzeit eine eingebaute Funktion, die beliebige Eingaben akzeptiert und a zurückgibt string.

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

  • Bewegen Sie den Mauszeiger über die const value Definition, um zu sehen, ob das Popover den richtigen Typ anzeigt.
  • Versuchen Sie, sich zu ändern "Bob" zu 123 und führen Sie die Probe erneut aus. In Ihrer Konsole sehen Sie, dass der Name ordnungsgemäß in die Zeichenfolge konvertiert wurde "123".

  • Sie haben ein Beispiel gegeben: “Wenn name waren eigentlich 123es wird umgewandelt in "123". Dies scheint falsch zu sein. Mein value kommt zurück {name: 123.. nicht {name:"123".. Wenn ich Ihren gesamten Code kopiere, füge ihn genau ein und nehme diese Änderung vor.

    – Joncom

    13. Mai 2020 um 8:39 Uhr

  • Komisch, bei mir funktioniert es. Versuchen Sie es hier: typescriptlang.org/play/index.html (unter Verwendung 123 Anstatt von "Bob").

    – Chowey

    14. Mai 2020 um 20:54 Uhr

  • Ich glaube nicht, dass Sie a definieren müssen Transformed Typ. Sie können einfach verwenden Object. type Descriptor<T extends Object> = { ... };

    – lovassoa

    16. Juni 2020 um 16:24 Uhr

  • Danke @lovasoa, du hast recht. Das Transformed Art ist völlig unnötig. Ich habe die Antwort entsprechend aktualisiert.

    – Chowey

    16. Juni 2020 um 20:09 Uhr


  • Wenn Sie tatsächlich überprüfen möchten, ob das JSON-Objekt die richtigen Typen hat, würden Sie dies tun nicht wollen 123 automatisch in einen String umgewandelt werden "123"da es sich um eine Zahl im JSON-Objekt handelt.

    – xuiqzy

    1. November 2020 um 21:28 Uhr

Dafür gibt es eine tolle Bibliothek 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 validiert den JSON vor dem Parsen

Verwenden app.quicktype.io um JSON in TypeScript sicher zu analysieren. Mehr dazu in Kürze.
JSON.parse() gibt den Typ zurück any und ist im “Happy Path” ausreichend, kann aber zu Fehlern im Zusammenhang mit der nachgelagerten Typsicherheit führen, was den Zweck von TypeScript zunichte macht. Zum Beispiel:

interface User {
  name: string,
  balance: number
}

const json = '{"name": "Bob", "balance": "100"}' //note the string "100"
const user:User = JSON.parse(json)

const newBalance = user.balance + user.balance * 0.05 //should be 105 after interest
console.log(newBalance ) //but it ends up as 1005 which is clearly wrong

Lassen Sie also Quicktype die schwere Arbeit erledigen und den Code generieren. Kopieren Sie die Zeichenfolge unten und fügen Sie sie in QuickType ein.

{
  "name": "Bob",
  "balance": 100
}

Achten Sie darauf, zu wählen TypeScript als Sprache und aktivieren Sie „Verify JSON.parse results at runtime“

Jetzt können wir Ausnahmen (falls vorhanden) zum Zeitpunkt des Parsens defensiv behandeln und verhindern, dass Fehler nachgelagert auftreten.

import { Convert, User } from "./user";

const json =
  '{"firstName": "Kevin", "lastName": "Le", "accountBalance": "100"}';

try {
  const user = Convert.toUser(json);
  console.log(user);
} catch (e) {
  console.log("Handle error", e);
}

user.ts ist die von Quicktype generierte Datei.

// To parse this data:
//
//   import { Convert, User } from "./file";
//
//   const user = Convert.toUser(json);
//
// These functions will throw an error if the JSON doesn't
// match the expected interface, even if the JSON is valid.

export interface User {
    name:    string;
    balance: number;
}

// Converts JSON strings to/from your types
// and asserts the results of JSON.parse at runtime
export class Convert {
    public static toUser(json: string): User {
        return cast(JSON.parse(json), r("User"));
    }

    public static userToJson(value: User): string {
        return JSON.stringify(uncast(value, r("User")), null, 2);
    }
}

function invalidValue(typ: any, val: any, key: any = ''): never {
    if (key) {
        throw Error(`Invalid value for key "${key}". Expected type ${JSON.stringify(typ)} but got ${JSON.stringify(val)}`);
    }
    throw Error(`Invalid value ${JSON.stringify(val)} for type ${JSON.stringify(typ)}`, );
}

function jsonToJSProps(typ: any): any {
    if (typ.jsonToJS === undefined) {
        const map: any = {};
        typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ });
        typ.jsonToJS = map;
    }
    return typ.jsonToJS;
}

function jsToJSONProps(typ: any): any {
    if (typ.jsToJSON === undefined) {
        const map: any = {};
        typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ });
        typ.jsToJSON = map;
    }
    return typ.jsToJSON;
}

function transform(val: any, typ: any, getProps: any, key: any = ''): any {
    function transformPrimitive(typ: string, val: any): any {
        if (typeof typ === typeof val) return val;
        return invalidValue(typ, val, key);
    }

    function transformUnion(typs: any[], val: any): any {
        // val must validate against one typ in typs
        const l = typs.length;
        for (let i = 0; i < l; i++) {
            const typ = typs[i];
            try {
                return transform(val, typ, getProps);
            } catch (_) {}
        }
        return invalidValue(typs, val);
    }

    function transformEnum(cases: string[], val: any): any {
        if (cases.indexOf(val) !== -1) return val;
        return invalidValue(cases, val);
    }

    function transformArray(typ: any, val: any): any {
        // val must be an array with no invalid elements
        if (!Array.isArray(val)) return invalidValue("array", val);
        return val.map(el => transform(el, typ, getProps));
    }

    function transformDate(val: any): any {
        if (val === null) {
            return null;
        }
        const d = new Date(val);
        if (isNaN(d.valueOf())) {
            return invalidValue("Date", val);
        }
        return d;
    }

    function transformObject(props: { [k: string]: any }, additional: any, val: any): any {
        if (val === null || typeof val !== "object" || Array.isArray(val)) {
            return invalidValue("object", val);
        }
        const result: any = {};
        Object.getOwnPropertyNames(props).forEach(key => {
            const prop = props[key];
            const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined;
            result[prop.key] = transform(v, prop.typ, getProps, prop.key);
        });
        Object.getOwnPropertyNames(val).forEach(key => {
            if (!Object.prototype.hasOwnProperty.call(props, key)) {
                result[key] = transform(val[key], additional, getProps, key);
            }
        });
        return result;
    }

    if (typ === "any") return val;
    if (typ === null) {
        if (val === null) return val;
        return invalidValue(typ, val);
    }
    if (typ === false) return invalidValue(typ, val);
    while (typeof typ === "object" && typ.ref !== undefined) {
        typ = typeMap[typ.ref];
    }
    if (Array.isArray(typ)) return transformEnum(typ, val);
    if (typeof typ === "object") {
        return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val)
            : typ.hasOwnProperty("arrayItems")    ? transformArray(typ.arrayItems, val)
            : typ.hasOwnProperty("props")         ? transformObject(getProps(typ), typ.additional, val)
            : invalidValue(typ, val);
    }
    // Numbers can be parsed by Date but shouldn't be.
    if (typ === Date && typeof val !== "number") return transformDate(val);
    return transformPrimitive(typ, val);
}

function cast<T>(val: any, typ: any): T {
    return transform(val, typ, jsonToJSProps);
}

function uncast<T>(val: T, typ: any): any {
    return transform(val, typ, jsToJSONProps);
}

function a(typ: any) {
    return { arrayItems: typ };
}

function u(...typs: any[]) {
    return { unionMembers: typs };
}

function o(props: any[], additional: any) {
    return { props, additional };
}

function m(additional: any) {
    return { props: [], additional };
}

function r(name: string) {
    return { ref: name };
}

const typeMap: any = {
    "User": o([
        { json: "name", js: "name", typ: "" },
        { json: "balance", js: "balance", typ: 0 },
    ], false),
};

Benutzer-Avatar
jfu

Sie können zusätzlich Bibliotheken verwenden, die eine Typvalidierung Ihres json durchführen, wie z Sparkson. Sie ermöglichen es Ihnen, eine TypeScript-Klasse zu definieren, für die Sie Ihre Antwort analysieren möchten. In Ihrem Fall könnte dies Folgendes 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.

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

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

Sie möchten jedoch häufig ein JSON-Objekt analysieren und 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 nimmt eine JSON-Zeichenfolge und ein Objekt, das einzelne Funktionen enthält, die jedes Feld des von Ihnen erstellten Objekts laden. Sie können es wie folgt verwenden:

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

1300410cookie-checkSo analysieren Sie JSON-Strings in Typescript

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy