std.json

JSON auf Textebene: Sonderzeichen maskieren, Wohlgeformtheit grob prüfen, einen Einzelwert einlesen und flache Arrays über ein Pipe-getrenntes Zwischenformat verarbeiten. Ein Dokumentmodell mit Objektzugriff gibt es nicht.

std.string · Guide: Daten & Serialisierung · Standard Library

Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben.

Einsatz: einfache REST-Antworten auswerten, Zahlenlisten übergeben, Werte für die Ausgabe maskieren.

 
Was diese Unit nicht kann — vor dem ersten Einsatz lesen:

* Kein Objektzugriff. „Hole user.name aus diesem JSON„ ist mit std.json nicht möglich; parseValue meldet bei einem Objekt nur den Typ und lässt den Zielpuffer unberührt (#1435).
* Kein verlustfreier Durchlauf. toArraystringify macht aus [1,2,3] das Dokument ["1","2","3"] — aus Zahlen werden Zeichenketten (#1432).
* Keine Prüfung des Inhalts von Arrays und Objekten: [abc] gilt als gültig (#1434).
* Keine Steuerzeichen-Maskierung — das Ergebnis von JSONEscape kann ungültiges JSON sein (#1433).

Für echte Dokumentverarbeitung führt der Guide die gangbaren Wege auf.

Typ- und Fehlercodes

Konstante Wert Konstante Wert
JSON_NULL 0 ERR_JSON_OK 0
JSON_BOOL 1 ERR_JSON_INVALID 1
JSON_NUMBER 2 ERR_JSON_EXPECTED 2
JSON_STRING 3 ERR_JSON_EOF 3
JSON_ARRAY 4 ERR_JSON_ESCAPE 4
JSON_OBJECT 5

Die JSON_*-Werte liefert parseValue als Typ, die ERR_JSON_*-Werte liefert parseArray als Fehlercode. Von den fünf Fehlercodes verwendet die Unit nur ERR_JSON_OK und ERR_JSON_INVALID.

 
Rückgabewerte bedeuten je Funktion etwas anderes. parseArray gibt einen Fehlercode zurück (0 = Erfolg), parseValue einen Typ, toArray/stringify/JSONEscape den Zielzeiger. Das Ergebnis steht in allen Fällen im Zielpuffer, den der Aufrufer stellt.

Prüfen, einlesen, maskieren

Signatur Beschreibung
isValidJSON(s: pchar): bool Grobprüfung — bei Skalaren genau, bei […] und {…} nur die Klammerpaare
parseValue(dest, src): int64 Einzelwert; Rückgabe ist der JSON_*-Typ, der Wert landet in dest
parseArray(dest, src): int64 JSON-Array ins Pipe-Format; Rückgabe ist ein ERR_JSON_*-Code
toArray(dest, json_str): pchar wie parseArray, leert dest im Fehlerfall
JSONEscape(dest, src): pchar maskiert , \, Zeilenumbruch, Wagenrücklauf, Tabulator — und setzt Anführungszeichen
serializeValue(dest, value, json_type): pchar maskiert bei JSON_STRING, kopiert sonst unverändert
serializeArray(dest, src): pchar · stringify(dest, pipe_str): pchar Pipe-Format zurück nach JSON (identisch)

import std.json;
import std.alloc;
import std.string;

fn J(b: bool): pchar { if (b) { return "gueltig"; } return "ungueltig"; }

fn main(): int64 {
    var d: pchar := alloc(1024) as pchar;

    // Pruefen: Skalare werden genau geprueft, Arrays und Objekte nur auf Klammerpaare
    PrintLn(StrConcat("'{\"a\":1}'  ", J(isValidJSON("{\"a\":1}"c))));
    PrintLn(StrConcat("'[1,2,3]'   ", J(isValidJSON("[1,2,3]"c))));
    PrintLn(StrConcat("'nonsense'  ", J(isValidJSON("nonsense"c))));
    PrintLn(StrConcat("'12.5'      ", J(isValidJSON("12.5"c))));
    PrintLn(StrConcat("'12.5.7'    ", J(isValidJSON("12.5.7"c))));
    PrintLn(StrConcat("'[abc]'     ", J(isValidJSON("[abc]"c))));

    // Array einlesen: der Rueckgabewert ist ein Fehlercode, das Ergebnis steht in dest
    var rc: int64 := parseArray(d, "[1,2,3]"c);
    PrintLn(StrConcat(StrConcat("parseArray rc=", IntToStr(rc)), StrConcat("  Pipe-Form: ", d)));

    // Einzelwert einlesen: der Rueckgabewert ist der Typ
    var typ: int64 := parseValue(d, "\"Hallo\""c);
    PrintLn(StrConcat(StrConcat("parseValue Typ=", IntToStr(typ)), StrConcat("  Inhalt: ", d)));

    // Maskieren fuer die Ausgabe
    JSONEscape(d, "Er sagte \"Hallo\"\nund ging"c);
    PrintLn(StrConcat("JSONEscape: ", d));
    return 0;
}

'{"a":1}'  gueltig
'[1,2,3]'   gueltig
'nonsense'  ungueltig
'12.5'      gueltig
'12.5.7'    ungueltig
'[abc]'     gueltig
parseArray rc=0  Pipe-Form: 1|2|3
parseValue Typ=3  Inhalt: Hallo
JSONEscape: "Er sagte \"Hallo\"\nund ging"

Die Skalarprüfung ist seit #1269 belastbar: nonsense und 12.5.7 werden abgewiesen, true/false/null namentlich verglichen. Nur der Inhalt von Arrays und Objekten bleibt ungeprüft — dort zählt allein, ob sich zur öffnenden Klammer eine schließende findet.

 
JSONEscape maskiert keine Steuerzeichen. Alles unter U+0020 außer Zeilenumbruch, Wagenrücklauf und Tabulator geht roh durch; RFC 8259 verlangt für jedes dieser Zeichen eine \u00XX-Folge. Ein Byte 0x01 aus einer Protokollzeile oder einem Datenbankfeld macht das Ergebnis für strenge Parser unlesbar (#1433).

Umgekehrt versteht die Unit \uXXXX beim Einlesen nicht — aus einem maskierten A wird u0041.

Das Pipe-Format

Arrays werden nicht als Struktur geführt, sondern als eine Zeichenkette, in der die Elemente durch | getrennt sind. parseArray erzeugt sie, stringify macht daraus wieder JSON.

import std.json;
import std.alloc;
import std.string;

fn Zeig(json: pchar): void {
    var pipe: pchar := alloc(1024) as pchar;
    var back: pchar := alloc(1024) as pchar;
    toArray(pipe, json);
    stringify(back, pipe);
    PrintLn(StrConcat(StrConcat(json, "  ->  "), StrConcat(pipe, StrConcat("  ->  ", back))));
}

fn main(): int64 {
    Zeig("[1,2,3]"c);
    Zeig("[\"a\",\"b\"]"c);
    Zeig("[1,[2,3]]"c);
    Zeig("[\"a|b\",\"c\"]"c);
    return 0;
}

[1,2,3]  ->  1|2|3  ->  ["1","2","3"]
["a","b"]  ->  "a"|"b"  ->  ["\"a\"","\"b\""]
[1,[2,3]]  ->  1|[2,3]  ->  ["1","[2,3]"]
["a|b","c"]  ->  "a|b"|"c"  ->  ["\"a","b\"","\"c\""]

 
Der Durchlauf ist nicht verlustfrei (#1432). Vier Dinge gehen schief:

- Zahlen werden zu Zeichenketten: [1,2,3] kommt als ["1","2","3"] zurück, weil serializeArray jedes Element in Anführungszeichen setzt.
- Zeichenketten werden doppelt maskiert: die Anführungszeichen stehen im Pipe-Format noch drin.
- Verschachtelung wird Text: aus dem inneren [2,3] wird die Zeichenkette "[2,3]".
- Ein | im Wert erzeugt zusätzliche Elemente — aus zwei werden drei.

Brauchbar ist das Format also für flache Arrays gleichartiger Werte ohne Pipe-Zeichen. Wer Zahlen zurückbekommen will, liest die Pipe-Form selbst mit StrSplit aus, statt über stringify zu gehen.

Die Pipe-Form lässt sich mit StrSplit aus std.string weiterverarbeiten — das ist der vorgesehene Weg, an die einzelnen Werte zu kommen.


Einzelwerte

parseValue erkennt den Typ am ersten Zeichen und legt den Wert in dest ab:

Eingabe Rückgabe dest
"Hallo" JSON_STRING (3) Hallo — Anführungszeichen entfernt, Maskierungen aufgelöst
[1,2] JSON_ARRAY (4) Pipe-Form 1|2
{"a":1} JSON_OBJECT (5) unverändert — der Puffer wird nicht angefasst
true / false JSON_BOOL (1) true / false
null JSON_NULL (0) null
42, 3.5 JSON_NUMBER (2) die Ziffern
alles andere JSON_NUMBER (2) die Eingabe unverändert
 
Zwei Fallen in dieser Tabelle (#1435): Bei einem Objekt bleibt der Zielpuffer unberührt — dort steht dann noch, was ein vorheriger Aufruf hinterlassen hat. Und unbekannte Eingaben werden als Zahl gemeldet: Muell liefert JSON_NUMBER und landet unverändert im Puffer. Vor der Auswertung also den Typ prüfen und bei JSON_OBJECT den Puffer selbst leeren.

Fallstricke

  • Zielpuffer gehören dem Aufrufer und werden nicht auf Größe geprüft. Für JSONEscape sind zwei Byte je Eingabezeichen plus zwei für die Anführungszeichen sicher.
  • JSONEscape setzt die Anführungszeichen selbst — wer sie zusätzlich schreibt, bekommt doppelte.
  • isValidJSON ist keine Eingangsprüfung für fremde Daten.
  • Kein Objektzugriff, keine Pfadausdrücke, keine Zahlen-Umwandlung — was aus parseValue kommt, ist Text; für int64 anschließend StrToInt aus std.string.
  • Kein Whitespace-Handling zwischen Elementen: [1, 2] ergibt im Pipe-Format 1| 2 — mit führendem Leerzeichen im zweiten Element.
  • serializeValue maskiert nur bei JSON_STRING. Für alle anderen Typen kopiert es die Eingabe wörtlich — auch dann, wenn dort etwas steht, das kein gültiges JSON ist.

Weiterführend: Guide: Daten & Serialisierung · std.string · std.xml · std.net.http · Standard Library

Quelle: std/json.lyx (480 Zeilen) · Autor: Andreas Röne · Copyright: 2024–2025 Andreas Röne

Letzte Aktualisierung: 2026-08-13 — Seite gegen std/json.lyx überarbeitet: zwei lauffähige Beispiele mit echter Ausgabe, Rückgabewert-Bedeutungen je Funktion, Verhaltenstabelle für parseValue, Grenzen des Pipe-Formats, Fallstricke. Belegt und als Issue erfasst: Pipe-Durchlauf verliert Typen (#1432), JSONEscape maskiert keine Steuerzeichen (#1433), isValidJSON prüft […]/{…} nur auf Klammerpaare (#1434), parseValue lässt dest bei Objekten unberührt (#1435). Geprüft mit lyxc 1.0.21A.