JSON ist eines der beliebtesten Serialisierungsformate. Es ist für Menschen lesbar, relativ kurz und kann von jeder Webanwendung, die JavaScript verwendet, problemlos analysiert werden. Go als moderne Programmiersprache bietet erstklassige Unterstützung für die JSON-Serialisierung in seiner Standardbibliothek.
Es gibt aber einige Winkel und Winkel. In diesem Lernprogramm erfahren Sie, wie Sie willkürliche und strukturierte Daten effektiv in und aus JSON serialisieren und deserialisieren. Sie lernen auch, wie Sie mit fortgeschrittenen Szenarien wie Serialisierungsumgebungen umgehen.
Go unterstützt mehrere Serialisierungsformate im Kodierungspaket seiner Standardbibliothek. Eines davon ist das beliebte JSON-Format. Sie serialisieren Golang-Werte mit der Marshal () - Funktion in ein Byte-Byte. Mit der Funktion Unmarshal () deserialisieren Sie ein Stück Byte in einen Golang-Wert. So einfach ist das. Die folgenden Begriffe sind im Kontext dieses Artikels gleichwertig:
Ich bevorzuge die Serialisierung, weil sie die Tatsache widerspiegelt, dass Sie eine potenziell hierarchische Datenstruktur in einen oder aus einem Bytestrom konvertieren.
Die Marshal () - Funktion kann alles annehmen, was in Go die leere Schnittstelle bedeutet und ein Stück Byte und Fehler zurückgibt. Hier ist die Unterschrift:
func Marshal (v interface ) ([] Byte, Fehler)
Wenn Marshal () den Eingabewert nicht serialisiert, wird ein Nicht-Null-Fehler zurückgegeben. Marshal () hat einige strikte Einschränkungen (wir werden später sehen, wie man sie mit benutzerdefinierten Marshallern besiegt):
Die Unmarshal () - Funktion benötigt ein Byte-Slice, das hoffentlich gültige JSON darstellt, und eine Zielschnittstelle, die in der Regel ein Zeiger auf eine Struktur oder einen Basistyp ist. Es deserialisiert den JSON auf generische Weise in die Schnittstelle. Wenn die Serialisierung fehlgeschlagen ist, wird ein Fehler zurückgegeben. Hier ist die Unterschrift:
func Unmarshal (data [] byte, v interface ) fehler
Sie können einfache Typen wie das Json-Paket problemlos serialisieren. Das Ergebnis ist kein vollwertiges JSON-Objekt, sondern eine einfache Zeichenfolge. Hier wird das int 5 zum Byte-Array [53] serialisiert, das der Zeichenfolge "5" entspricht..
// Serialize int var x = 5 Bytes, err: = json.Marshal (x) if err! = Nil fmt.Println ("Can't serislize", x) fmt.Printf ("% v =>% v , '% v' \ n ", x, Bytes, Zeichenfolge (Bytes)) // Deserialize int var r int err = json.Unmarshal (Bytes, & r) if err! = nil fmt.Println (" Kann nicht deserislize ", Bytes) fmt.Printf ("% v =>% v \ n ", Bytes, r) Ausgabe: - 5 => [53], '5' - [53] => 5
Wenn Sie versuchen, nicht unterstützte Typen wie eine Funktion zu serialisieren, wird eine Fehlermeldung angezeigt:
// Versuch, eine Funktion zu serialisieren foo: = func () fmt.Println ("foo () here")) Bytes, err = json.Marshal (foo) if err! = Nil fmt.Println (err) Ausgabe : json: nicht unterstützter Typ: func ()
Die Stärke von JSON ist, dass es beliebige hierarchische Daten sehr gut darstellen kann. Das JSON-Paket unterstützt dies und verwendet die generische leere Schnittstelle (Schnittstelle ) zur Darstellung einer beliebigen JSON-Hierarchie. Hier ist ein Beispiel für die Deserialisierung und spätere Serialisierung eines binären Baums, bei dem jeder Knoten einen int-Wert und zwei linke und rechte Zweige hat, die einen anderen Knoten enthalten können oder Null sind.
Die JSON-Null entspricht der Go-Null. Wie Sie in der Ausgabe sehen können, wird die json.Unmarshal ()
Funktion hat den JSON-Blob erfolgreich in eine Go-Datenstruktur konvertiert, die aus einer verschachtelten Karte von Schnittstellen besteht und den Werttyp als int beibehält. Das json.Marshal ()
Das serialisierte Objekt wurde mit derselben JSON-Darstellung erfolgreich serialisiert.
// beliebig geschachteltes JSON dd: = '"value": 3, "left": "value": 1, "left": null, "right": "value": 2, "left": null, "right": null, "right": "value": 4, "left": null, "right": null 'var obj interface err = json.Unmarshal ([] byte (dd) , & obj) if err! = nil fmt.Println (err) else fmt.Println ("-------- \ n", obj) data, err = json.Marshal (obj), wenn err ! = nil fmt.Println (err) else fmt.Println ("-------- \ n", Zeichenfolge (Daten)) Ausgabe: -------- map [right : map [wert: 4 übrig:Recht: ] Wert: 3 links: Karte [links: rechts: Karte [Wert: 2 links: Recht: ] value: 1]] -------- "left": "left": null, "right": "left": null, "right": null, "value": 2, "value": 1, "right": "left": null, "right": null, "value": 4, "value": 3
Um die generischen Karten von Schnittstellen zu durchlaufen, müssen Sie Typ-Assertionen verwenden. Zum Beispiel:
func dump (obj interface ) Fehler if obj == nil fmt.Println ("nil") return nil Schalter obj. (Typ) case bool: fmt.Println (obj. (bool)) case int: fmt.Println (obj. (int)) case float64: fmt.Println (obj. (float64)) case string: fmt.Println (obj. (string)) case map [string] interface : für k, v: = range (obj. (map [string] interface )) fmt.Printf ("% s:", k) err: = dump (v) wenn err! = nil return err default: Rückgabefehler. Neu (fmt.Sprintf ("Nicht unterstützter Typ:% v", obj)) Rückgabewert
Die Arbeit mit strukturierten Daten ist oft die bessere Wahl. Go bietet eine hervorragende Unterstützung für die Serialisierung von JSON nach / von structs
über seine struct
Stichworte. Lass uns einen erstellen struct
das entspricht unserem JSON-Baum und einem intelligenteren Dump ()
Funktion, die es druckt:
type Tree struct Wert int left * Tree right * Tree Funktion (t * Tree) Dump (Einrückungszeichenfolge) fmt.Println (indent + "value:", t.value) fmt.Print (indent + "left:" ) Wenn t.left == nil fmt.Println (nil) else fmt.Println () t.left.Dump (indent + "") fmt.Print (indent + "right:") wenn t.right == nil fmt.Println (nil) else fmt.Println () t.right.Dump (indent + "")
Das ist großartig und viel sauberer als der willkürliche JSON-Ansatz. Aber geht das? Nicht wirklich. Es ist kein Fehler aufgetreten, aber unser Baumobjekt wird nicht von JSON aufgefüllt.
jsonTree: = '"value": 3, "left": "value": 1, "left": null, "right": "value": 2, "left": null, "right": null , "right": "value": 4, "left": null, "right": null 'var tree Baum err = json.Unmarshal ([] Byte (dd), & tree), wenn err! = nil fmt.Printf ("- Baum kann nicht deserislize werden, Fehler:% v \ n", err) else tree.Dump ("") Ausgabe: Wert: 0 übrig:Recht:
Das Problem ist, dass die Tree-Felder privat sind. Die JSON-Serialisierung funktioniert nur in öffentlichen Bereichen. So können wir das machen struct
Felder öffentlich. Das json-Paket ist intelligent genug, um die Kleinbuchstaben "value", "left" und "right" transparent in ihre entsprechenden Großbuchstaben umzuwandeln.
type Tree struct Wert int 'json: "value"' Left * Tree 'json: "left"' Right * Tree 'json: "right"' Ausgabe: Wert: 3 übrig: Wert: 1 übrig:Rechts: Wert: 2 Links: Recht: rechts: Wert: 4 übrig: Recht:
Das json-Paket ignoriert automatisch nicht zugeordnete Felder in der JSON-Datenbank sowie private Felder in Ihrer struct
. Manchmal möchten Sie jedoch bestimmte Schlüssel in der JSON einem Feld mit einem anderen Namen in Ihrem zuordnen struct
. Sie können verwenden struct
Tags dafür. Nehmen wir beispielsweise an, wir fügen ein weiteres Feld mit dem Namen "label" zur JSON hinzu, müssen es jedoch einem Feld mit dem Namen "Tag" in unserer Struktur zuordnen.
type Tree struct Wert int Tag-String 'json: "label"' Left * Tree Right * Tree func (t * Tree) Dump (Einrückungsstring) fmt.Println (indent + "value:", t.Value) if t.Tag! = "" fmt.Println (indent + "tag:", t.Tag) fmt.Print (indent + "left:") wenn t.Left == nil fmt.Println (nil) else fmt.Println () t.Left.Dump (indent + "") fmt.Print (indent + "right:"), wenn t.Right == nil fmt.Println (nil) else fmt.Println () t.Right.Dump (Einzug + "")
Hier ist der neue JSON mit dem Stammknoten des Baums, der als "root" gekennzeichnet ist, ordnungsgemäß in das Feld "Tag" serialisiert und in der Ausgabe gedruckt:
dd: = '"label": "root", "value": 3, "left": "value": 1, "left": null, "right": "value": 2, "left" : null, "rechts": null, "rechts": "value": 4, "links": null, "rechts": null 'var tree Baum err = json.Unmarshal ([] byte (dd ), & tree) if err! = nil fmt.Printf ("- Baum kann nicht deserislize werden, Fehler:% v \ n", err) else tree.Dump ("") Ausgabe: Wert: 3 Tag: Wurzel links: Wert: 1 links:Rechts: Wert: 2 Links: Recht: rechts: Wert: 4 übrig: Recht:
Sie möchten häufig Objekte serialisieren, die nicht den strengen Anforderungen der Funktion Marshal () entsprechen. Sie können beispielsweise eine Karte mit int-Schlüsseln serialisieren. In diesen Fällen können Sie einen benutzerdefinierten Marshaller / Unmarshaller schreiben, indem Sie den Marshaler
und Unmarshaler
Schnittstellen.
Ein Hinweis zur Rechtschreibung: In Go wird eine Schnittstelle mit einer einzelnen Methode benannt, indem das Suffix "er" an den Methodennamen angehängt wird. Obwohl die gebräuchlichste Schreibweise "Marshaller" (mit doppeltem L) ist, lautet der Name der Schnittstelle nur "Marshaler" (einzelnes L)..
Hier sind die Marshaler- und Unmarshaler-Schnittstellen:
Typ Marshaler-Schnittstelle MarshalJSON () ([] Byte, Fehler) Typ Unmarshaler-Schnittstelle UnmarshalJSON ([] Byte) Fehler
Bei der benutzerdefinierten Serialisierung müssen Sie einen Typ erstellen, auch wenn Sie einen integrierten Typ oder eine Komposition von integrierten Typen wie map [int] string
. Hier definiere ich einen Typ namens IntStringMap
und implementieren die Marshaler
und Unmarshaler
Schnittstellen für diesen Typ.
Das MarshalJSON ()
Methode erstellt ein map [string] string
, wandelt jeden seiner eigenen int-Schlüssel in einen String um und serialisiert die Map mit String-Schlüsseln unter Verwendung des Standards json.Marshal ()
Funktion.
type IntStringMap map [int] string func (m * IntStringMap) MarshalJSON () ([] Byte, Fehler) ss: = map [Zeichenfolge] string für k, v: = range * m i: = strconv.Itoa (k) ss [i] = v return json.Marshal (ss)
Die UnmarshalJSON () - Methode macht genau das Gegenteil. Es deserialisiert das Datenbyte-Array in ein map [string] string
und konvertiert dann jeden String-Schlüssel in ein int und füllt sich damit.
func (m * IntStringMap) UnmarshalJSON (data [] byte) Fehler ss: = map [Zeichenfolge] string err: = json.Unmarshal (data, & ss), wenn err! = nil return err für k, v: = Bereich ss i, err: = strconv.Atoi (k) wenn err! = nil return err (* m) [i] = v return nil
So verwenden Sie es in einem Programm:
m: = IntStringMap 4: "vier", 5: "fünf" Daten, err: = m.MarshalJSON () if err! = nil fmt.Println (err) fmt.Println ("IntStringMap to JSON:" , string (data)) m = IntStringMap jsonString: = [] Byte ("" 1 ":" Eins "," 2 ":" Zwei "") m.UnmarshalJSON ( jsonString) fmt.Printf ("IntStringMap von JSON:% v \ n", m) fmt.Println ("m [1]:", m [1], "m [2]:", m [2]) Ausgabe : IntStringMap nach JSON: "4": "vier", "5": "fünf" IntStringMap von JSON: map [2: zwei 1: eins] m [1]: ein m [2]: zwei
Go-Enumerationen können ziemlich ärgerlich sein, um sie zu serialisieren. Die Idee, einen Artikel über die Go-Json-Serialisierung zu schreiben, kam aus einer Frage, die ein Kollege mir gefragt hat, wie man Enumerationen serialisiert. Hier ist ein Go enum
. Die Konstanten Null und Eins sind gleich den Ints 0 und 1.
Typ EnumType int const (Zero EnumType = Iota One)
Während Sie denken, dass es ein int ist und in vieler Hinsicht ist es, können Sie es nicht direkt serialisieren. Sie müssen einen benutzerdefinierten Marshaller / Unmarshaler schreiben. Das ist nach dem letzten Abschnitt kein Problem. Folgende MarshalJSON ()
und UnmarshalJSON ()
Serialisiert / deserialisiert die Konstanten ZERO und ONE zu / von den entsprechenden Strings "Zero" und "One".
func (e * EnumType) UnmarshalJSON (data [] byte) -Fehler var s string err: = json.Unmarshal (data, & s) wenn err! = nil return err value, ok: = map [string] EnumType " Null ": Null," Eins ": Eins [s] if! Ok Rückgabefehler. Neu (" Ungültiger EnumType-Wert ") * e = Wert Rückgabe nil func (e * EnumType) MarshalJSON () ([] byte) , error) value, ok: = map [EnumType] string Null: "Null", Eins: "Eins" [* e] if! ok return nil, errors.New ("Ungültiger EnumType-Wert") zurück json.Marshal (Wert)
Versuchen wir das einzubetten EnumType
in einem struct
und serialisieren Sie es. Die Hauptfunktion erstellt eine EnumContainer
und initialisiert es mit dem Namen "Uno" und einem Wert von unserem enum
Konstante EIN
, was gleich dem int 1 ist.
type EnumContainer struct Name Zeichenfolge Wert EnumType func main () x: = Ein ec: = EnumContainer "Uno", x, s, err: = json.Marshal (ec), wenn err! = nil fmt.Printf ("fail!") var ec2 EnumContainer err = json.Unmarshal (s, & ec2) fmt.Println (ec2.Name, ":", ec2.Value) Ausgabe: Uno: 0
Die erwartete Ausgabe ist "Uno: 1", aber stattdessen "Uno: 0". Was ist passiert? Der Marshall / Unmarshal-Code enthält keinen Fehler. Es stellt sich heraus, dass Sie Enummen nicht nach Wert einbetten können, wenn Sie sie serialisieren möchten. Sie müssen einen Zeiger auf die Enumeration einbetten. Hier ist eine modifizierte Version, bei der das wie erwartet funktioniert:
type EnumContainer struct Name Zeichenfolge Wert * EnumType func main () x: = Ein ec: = EnumContainer "Uno", & x, s, err: = json.Marshal (ec) wenn err! = nil fmt. Printf ("fail!") Var ec2 EnumContainer err = json.Unmarshal (s, & ec2) fmt.Println (ec2.Name, ":", * ec2.Value) Ausgabe: Uno: 1
Go bietet viele Optionen zum Serialisieren und Deserialisieren von JSON. Es ist wichtig, die Besonderheiten des Kodierungs- / Json-Pakets zu verstehen, um die Leistungsfähigkeit nutzen zu können.
In diesem Tutorial haben Sie die ganze Macht in die Hand, einschließlich der Serialisierung der schwer fassbaren Go-Enums.
Serialisieren Sie einige Objekte!