Kapitel 22. JSON
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 22. JSON

JSON (JavaScript Object Notation) ist ein Nur-Text-Format zur Datenspeicherung. Es ist als Datenaustauschformat für Webservices, Konfigurationsdateien und vieles mehr sehr beliebt geworden. ECMAScript 5 verfügt über eine API zur Konvertierung von einem String im JSON-Format in einen JavaScript-Wert (Parsen) und umgekehrt (Stringifizieren).

Hintergrund

Dieser Abschnitt erklärt, was JSON ist und wie es entstanden ist.

Geschichte

Douglas Crockford entdeckte JSON im Jahr 2001. Er gab ihm einen Namen und erstellte eine Spezifikation unter http://json.org

Ich habe JSON entdeckt. Ich behaupte nicht, JSON erfunden zu haben, da es bereits in der Natur existierte. Was ich getan habe, war, es zu finden, ihm einen Namen zu geben und zu beschreiben, wie es nützlich ist. Ich behaupte nicht, die erste Person zu sein, die es entdeckt hat; ich weiß, dass es andere Leute gab, die es mindestens ein Jahr vor mir entdeckt haben. Das früheste Vorkommen, das ich gefunden habe, war, dass jemand bei Netscape JavaScript-Array-Literale für die Datenkommunikation bereits 1996 verwendete, was mindestens fünf Jahre war, bevor ich auf die Idee stieß.

Anfangs wollte Crockford, dass JSON den Namen JavaScript Markup Language hat, aber das Akronym JSML war bereits von der JSpeech Markup Language belegt.

Die JSON-Spezifikation wurde in viele menschliche Sprachen übersetzt, und es gibt jetzt Bibliotheken für viele Programmiersprachen, die das Parsen und Generieren von JSON unterstützen.

Grammatik

Douglas Crockford erstellte eine JSON-Visitenkarte mit einem Logo auf der Vorderseite (siehe Abbildung 22-1) und der vollständigen Grammatik auf der Rückseite (siehe Abbildung 22-2). Das macht visuell deutlich, wie positiv einfach JSON ist.

Die Grammatik kann wie folgt transkribiert werden

object

{ }

{ Mitglieder }

Mitglieder

Paar

Paar , Mitglieder

Paar
String : Wert
Array

[ ]

[ Elemente ]

Elemente

Wert

Wert , Elemente

Wert

string

number

object

Array

true

false

null

string

""

" Zeichen "

Zeichen

Zeichen

Zeichen Zeichen

Zeichen

beliebiger-Unicode-Zeichen-außer-"-oder-\-oder-Steuerzeichen

\" \\ \/ \b \f \n \r \t

\u vier-Hex-Ziffern

number

Ganzzahl

Ganzzahl Bruch

Ganzzahl Exponent

Ganzzahl Bruch Exponent

Ganzzahl

Ziffer

Ziffer1-9 Ziffern

- Ziffer

- Ziffer1-9 Ziffern

Bruch
. Ziffern
Exponent
e Ziffern
Ziffern

Ziffer

Ziffer Ziffern

e

e e+ e-

E E+ E-

Die globale Variable JSON dient als Namespace für Funktionen, die Strings mit JSON-Daten erzeugen und parsen.

JSON.stringify(value, replacer?, space?)

JSON.stringify(value, replacer?, space?) übersetzt den JavaScript-Wert value in einen String im JSON-Format. Es hat zwei optionale Argumente.

Der optionale Parameter replacer wird verwendet, um value vor dem Stringifizieren zu ändern. Er kann sein

  • Ein Knotenbesucher (siehe Daten über Knotenbesucher transformieren), der den Baum von Werten transformiert, bevor er stringifiziert wird. Zum Beispiel

    function replacer(key, value) {
        if (typeof value === 'number') {
            value = 2 * value;
        }
        return value;
    }

    Verwendung des Replacer

    > JSON.stringify({ a: 5, b: [ 2, 8 ] }, replacer)
    '{"a":10,"b":[4,16]}'
  • Eine Whitelist von Eigenschaftsschlüsseln, die alle Eigenschaften (von Nicht-Array-Objekten) verbirgt, deren Schlüssel nicht in der Liste enthalten sind. Zum Beispiel

    > JSON.stringify({foo: 1, bar: {foo: 1, bar: 1}}, ['bar'])
    '{"bar":{"bar":1}}'

    Die Whitelist hat keine Auswirkungen auf Arrays

    > JSON.stringify(['a', 'b'], ['0'])
    '["a","b"]'

Der optionale Parameter space beeinflusst die Formatierung der Ausgabe. Ohne diesen Parameter ist das Ergebnis von stringify eine einzelne Textzeile

> console.log(JSON.stringify({a: 0, b: ['\n']}))
{"a":0,"b":["\n"]}

Mit ihm werden Zeilenumbrüche eingefügt und jede Verschachtelungsebene über Arrays und Objekte erhöht die Einrückung. Es gibt zwei Möglichkeiten, anzugeben, wie eingerückt werden soll

Eine Zahl

Multipliziere die Zahl mit der Einrückungsebene und rücke die Zeile um so viele Leerzeichen ein. Zahlen kleiner als 0 werden als 0 interpretiert; Zahlen größer als 10 werden als 10 interpretiert

> console.log(JSON.stringify({a: 0, b: ['\n']}, null, 2))
{
  "a": 0,
  "b": [
    "\n"
  ]
}
Ein String

Um einzurücken, wiederhole den gegebenen String einmal für jede Einrückungsebene. Nur die ersten 10 Zeichen des Strings werden verwendet

> console.log(JSON.stringify({a: 0, b: ['\n']}, null, '|--'))
{
|--"a": 0,
|--"b": [
|--|--"\n"
|--]
}

Daher gibt die folgende Invokation von JSON.stringify() ein Objekt als schön formatierten Baum aus

JSON.stringify(data, null, 4)

Von JSON.stringify() ignorierte Daten

In Objekten berücksichtigt JSON.stringify() nur aufzählbare eigene Eigenschaften (siehe Eigenschaftenattribute und Eigenschaftsdeskriptoren). Das folgende Beispiel zeigt, dass die nicht aufzählbare eigene Eigenschaft obj.foo ignoriert wird:

> var obj = Object.defineProperty({}, 'foo', { enumerable: false, value: 7 });
> Object.getOwnPropertyNames(obj)
[ 'foo' ]
> obj.foo
7
> JSON.stringify(obj)
'{}'

Wie JSON.stringify() Werte behandelt, die von JSON nicht unterstützt werden (wie Funktionen und undefined), hängt davon ab, wo es sie findet. Ein nicht unterstützter Wert selbst führt dazu, dass stringify() undefined anstelle eines Strings zurückgibt

> JSON.stringify(function () {})
undefined

Eigenschaften, deren Werte nicht unterstützt werden, werden einfach ignoriert

> JSON.stringify({ foo: function () {} })
'{}'

Nicht unterstützte Werte in Arrays werden als nulls stringifiziert

> JSON.stringify([ function () {} ])
'[null]'

Die toJSON()-Methode

Wenn JSON.stringify() auf ein Objekt trifft, das eine toJSON-Methode hat, verwendet es diese Methode, um einen zu stringifizierenden Wert zu erhalten. Zum Beispiel:

> JSON.stringify({ toJSON: function () { return 'Cool' } })
'"Cool"'

Daten haben bereits eine toJSON-Methode , die einen ISO 8601-Datumsstring erzeugt:

> JSON.stringify(new Date('2011-07-29'))
'"2011-07-28T22:00:00.000Z"'

Die vollständige Signatur einer toJSON-Methode lautet wie folgt

function (key)

Der key-Parameter ermöglicht es Ihnen, je nach Kontext unterschiedlich zu stringifizieren. Es ist immer ein String und gibt an, wo Ihr Objekt im Elternobjekt gefunden wurde

Wurzelposition
Der leere String
Eigenschaftswert
Der Eigenschaftsschlüssel
Array-Element
Der Index des Elements als String

Ich werde toJSON() anhand des folgenden Objekts demonstrieren

var obj = {
    toJSON: function (key) {
        // Use JSON.stringify for nicer-looking output
        console.log(JSON.stringify(key));
        return 0;
    }
};

Wenn Sie JSON.stringify() verwenden, wird jedes Vorkommen von obj durch 0 ersetzt. Die toJSON()-Methode wird darüber informiert, dass obj am Eigenschaftsschlüssel 'foo' und am Array-Index 0 angetroffen wurde

> JSON.stringify({ foo: obj, bar: [ obj ]})
"foo"
"0"
'{"foo":0,"bar":[0]}'

Die integrierten toJSON()-Methoden sind wie folgt:

  • Boolean.prototype.toJSON()
  • Number.prototype.toJSON()
  • String.prototype.toJSON()
  • Date.prototype.toJSON()

JSON.parse(text, reviver?)

JSON.parse(text, reviver?) parst die JSON-Daten in text und gibt einen JavaScript-Wert zurück. Hier sind einige Beispiele:

> JSON.parse("'String'") // illegal quotes
SyntaxError: Unexpected token ILLEGAL
> JSON.parse('"String"')
'String'
> JSON.parse('123')
123
> JSON.parse('[1, 2, 3]')
[ 1, 2, 3 ]
> JSON.parse('{ "hello": 123, "world": 456 }')
{ hello: 123, world: 456 }

Der optionale Parameter reviver ist ein Knotenbesucher (siehe Daten über Knotenbesucher transformieren) und kann verwendet werden, um die geparsten Daten zu transformieren. In diesem Beispiel übersetzen wir Datums-Strings in Datums-Objekte:

function dateReviver(key, value) {
    if (typeof value === 'string') {
        var x = Date.parse(value);
        if (!isNaN(x)) { // valid date string?
            return new Date(x);
        }
    }
    return value;
}

Und hier ist die Interaktion

> var str = '{ "name": "John", "birth": "2011-07-28T22:00:00.000Z" }';
> JSON.parse(str, dateReviver)
{ name: 'John', birth: Thu, 28 Jul 2011 22:00:00 GMT }

Daten über Knotenbesucher transformieren

Sowohl JSON.stringify() als auch JSON.parse() ermöglichen es Ihnen, JavaScript-Daten zu transformieren, indem Sie eine Funktion übergeben:

  • JSON.stringify() ermöglicht es Ihnen, die JavaScript-Daten zu ändern, bevor sie in JSON umgewandelt werden.
  • JSON.parse() parst JSON und ermöglicht es Ihnen dann, die resultierenden JavaScript-Daten nachzubearbeiten.

Die JavaScript-Daten sind ein Baum, dessen Verbundknoten Arrays und Objekte sind und dessen Blätter primitive Werte (Booleans, Zahlen, Strings, null) sind. Nennen wir die Transformationsfunktion, die Sie übergeben, Knotenbesucher. Die Methoden iterieren über den Baum und rufen den Besucher für jeden Knoten auf. Sie hat dann die Möglichkeit, den Knoten zu ersetzen oder zu löschen. Der Knotenbesucher hat die Signatur

function nodeVisitor(key, value)

Die Parameter sind:

this
Das Elternteil des aktuellen Knotens.
Schlüssel
Ein Schlüssel, an dem sich der aktuelle Knoten innerhalb seines Elternteils befindet. key ist immer ein String.
Wert
Der aktuelle Knoten.

Der Wurzelknoten root hat kein Elternteil. Wenn root besucht wird, wird ein Pseudoparent für ihn erstellt und die Parameter haben die folgenden Werte

  • this ist { '': root }.
  • key ist ''.
  • value ist root.

Der Knotenbesucher hat drei Optionen für die Rückgabe eines Wertes

  • Geben Sie value unverändert zurück. Dann wird keine Änderung vorgenommen.
  • Geben Sie einen anderen Wert zurück. Dann wird der aktuelle Knoten durch ihn ersetzt.
  • Geben Sie undefined zurück. Dann wird der Knoten entfernt.

Das Folgende ist ein Beispiel für einen Knotenbesucher. Es protokolliert, welche Werte ihm übergeben wurden.

function nodeVisitor(key, value) {
    console.log([
        // Use JSON.stringify for nicer-looking output
        JSON.stringify(this), // parent
        JSON.stringify(key),
        JSON.stringify(value)
    ].join(' # '));
    return value; // don't change node
}

Verwenden wir diese Funktion, um zu untersuchen, wie die JSON-Methoden über JavaScript-Daten iterieren.

JSON.stringify()

Der spezielle Wurzelknoten kommt zuerst, in einer Präfix-Iteration (Elternteil vor Kindern). Der erste besuchte Knoten ist immer die Pseudowurzel. Die letzte Zeile, die nach jedem Aufruf angezeigt wird, ist der von stringify() zurückgegebene String:

> JSON.stringify(['a','b'], nodeVisitor)
{"":["a","b"]} # "" # ["a","b"]
["a","b"] # "0" # "a"
["a","b"] # "1" # "b"
'["a","b"]'

> JSON.stringify({a:1, b:2}, nodeVisitor)
{"":{"a":1,"b":2}} # "" # {"a":1,"b":2}
{"a":1,"b":2} # "a" # 1
{"a":1,"b":2} # "b" # 2
'{"a":1,"b":2}'

> JSON.stringify('abc', nodeVisitor)
{"":"abc"} # "" # "abc"
'"abc"'

JSON.parse()

Die Blätter kommen zuerst, in einer Postfix-Iteration (Kinder vor Elternteil). Der zuletzt besuchte Knoten ist immer die Pseudowurzel. Die letzte Zeile, die nach jedem Aufruf angezeigt wird, ist der von parse() zurückgegebene JavaScript-Wert:

> JSON.parse('["a","b"]', nodeVisitor)
["a","b"] # "0" # "a"
["a","b"] # "1" # "b"
{"":["a","b"]} # "" # ["a","b"]
[ 'a', 'b' ]

> JSON.parse('{"a":1, "b":2}', nodeVisitor)
{"a":1,"b":2} # "a" # 1
{"a":1,"b":2} # "b" # 2
{"":{"a":1,"b":2}} # "" # {"a":1,"b":2}
{ a: 1, b: 2 }

> JSON.parse('"hello"', nodeVisitor)
{"":"hello"} # "" # "hello"
'hello'

Weiter: 23. Standard-Globalvariablen