Einführung
Strukturen werden verwendet, um mehrere Informationen in einer Einheit zusammenzufassen. Diesecollections of information werden verwendet, um übergeordnete Konzepte zu beschreiben, z. B.Address, die ausStreet,City,State undPostalCode bestehen . Wenn Sie diese Informationen aus Systemen wie Datenbanken oder APIs lesen, können Sie mithilfe von Struktur-Tags steuern, wie diese Informationen den Feldern einer Struktur zugewiesen werden. Struct-Tags sind kleine Metadaten, die an Felder einer Struktur angehängt sind und Anweisungen für anderen Go-Code enthalten, der mit der Struktur zusammenarbeitet.
Wie sieht ein Struct-Tag aus?
Go-Struktur-Tags sind Anmerkungen, die nach dem Typ in einer Go-Struktur-Deklaration erscheinen. Jedes Tag besteht aus kurzen Zeichenfolgen, denen ein entsprechender Wert zugeordnet ist.
Ein struct-Tag sieht folgendermaßen aus, wobei das Tag mit Backtick+\ + `Zeichen versetzt ist:
type User struct {
Name string `example:"name"`
}
Anderer Go-Code ist dann in der Lage, diese Strukturen zu untersuchen und die Werte zu extrahieren, die bestimmten Schlüsseln zugewiesen sind, die er anfordert. Struct-Tags haben keine Auswirkung auf die Funktionsweise Ihres Codes, ohne dass ein anderer Code sie untersucht.
Probieren Sie dieses Beispiel aus, um zu sehen, wie Struktur-Tags aussehen und dass sie ohne Code aus einem anderen Paket keine Wirkung haben.
package main
import "fmt"
type User struct {
Name string `example:"name"`
}
func (u *User) String() string {
return fmt.Sprintf("Hi! My name is %s", u.Name)
}
func main() {
u := &User{
Name: "Sammy",
}
fmt.Println(u)
}
Dies wird Folgendes ausgeben:
OutputHi! My name is Sammy
In diesem Beispiel wird einUser-Typ mit einemName-Feld definiert. Das FeldName hat ein Struktur-Tag vonexample:"name" erhalten. Wir würden dieses spezielle Tag in der Konversation als "Beispiel-Struktur-Tag" bezeichnen, da es das Wort "Beispiel" als Schlüssel verwendet. Das Struktur-Tagexamplehat den Wert"name" für das FeldName. Für den TypUserdefinieren wir auch die MethodeString(), die für die Schnittstellefmt.Stringererforderlich ist. Dies wird automatisch aufgerufen, wenn wir den Typ anfmt.Println übergeben, und gibt uns die Möglichkeit, eine schön formatierte Version unserer Struktur zu erstellen.
Innerhalb des Körpers vonmain erstellen wir eine neue Instanz unseres TypsUser und übergeben sie anfmt.Println. Obwohl in der Struktur ein Struktur-Tag vorhanden war, hat dies keine Auswirkungen auf die Funktionsweise dieses Go-Codes. Es verhält sich genauso, wenn das struct-Tag nicht vorhanden ist.
Um mit Struktur-Tags etwas zu erreichen, muss ein anderer Go-Code geschrieben werden, um Strukturen zur Laufzeit zu untersuchen. Die Standardbibliothek enthält Pakete, die Struktur-Tags als Teil ihrer Operation verwenden. Am beliebtesten ist das Paketencoding/json.
Codierung von JSON
JavaScript Object Notation (JSON) ist ein Textformat zum Codieren von Datensammlungen, die unter verschiedenen Zeichenfolgenschlüsseln organisiert sind. Es wird häufig für die Kommunikation von Daten zwischen verschiedenen Programmen verwendet, da das Format so einfach ist, dass Bibliotheken vorhanden sind, um Daten in vielen verschiedenen Sprachen zu decodieren. Das Folgende ist ein Beispiel für JSON:
{
"language": "Go",
"mascot": "Gopher"
}
Dieses JSON-Objekt enthält zwei Schlüssel,language undmascot. Nach diesen Tasten folgen die zugehörigen Werte. Hier hat der Schlüssellanguage einen Wert vonGo undmascot wird der WertGopher zugewiesen.
Der JSON-Encoder in der Standardbibliothek verwendet Struktur-Tags als Anmerkungen, die dem Encoder angeben, wie Sie Ihre Felder in der JSON-Ausgabe benennen möchten. Diese JSON-Codierungs- und Decodierungsmechanismen finden Sie inencoding/jsonpackage.
Probieren Sie dieses Beispiel aus, um zu sehen, wie JSON ohne struct-Tags codiert wird:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string
Password string
PreferredFish []string
CreatedAt time.Time
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Dadurch wird die folgende Ausgabe gedruckt:
Output{
"Name": "Sammy the Shark",
"Password": "fisharegreat",
"CreatedAt": "2019-09-23T15:50:01.203059-04:00"
}
Wir haben eine Struktur definiert, die einen Benutzer mit Feldern beschreibt, die den Namen, das Kennwort und den Zeitpunkt der Benutzererstellung enthalten. Innerhalb der Funktionmain erstellen wir eine Instanz dieses Benutzers, indem wir Werte für alle Felder außerPreferredFish angeben (Sammy mag alle Fische). Wir haben dann die Instanz vonUser an die Funktionjson.MarshalIndent übergeben. Dies wird verwendet, damit wir die JSON-Ausgabe einfacher sehen können, ohne ein externes Formatierungswerkzeug zu verwenden. Dieser Aufruf könnte durchjson.Marshal(u) ersetzt werden, um JSON ohne zusätzliche Leerzeichen zu empfangen. Die zwei zusätzlichen Argumente fürjson.MarshalIndent steuern das Präfix der Ausgabe (das wir mit der leeren Zeichenfolge weggelassen haben) und die zum Einrücken zu verwendenden Zeichen, bei denen es sich hier um zwei Leerzeichen handelt. Alle ausjson.MarshalIndent erzeugten Fehler werden protokolliert und das Programm wird mitos.Exit(1) beendet. Schließlich wandeln wir die vonjson.MarshalIndent zurückgegebenen[]byte instring um und übergeben die resultierende Zeichenfolge anfmt.Println zum Drucken auf dem Terminal.
Die Felder der Struktur werden genau so angezeigt, wie wir sie benannt haben. Dies ist nicht der typische JSON-Stil, den Sie vielleicht erwarten, bei dem Kamelgehäuse für die Namen von Feldern verwendet werden. In diesem nächsten Beispiel werden Sie die Namen des Felds ändern, um dem Kamelfallstil zu folgen. Wie Sie beim Ausführen dieses Beispiels sehen werden, funktioniert dies nicht, da die gewünschten Feldnamen im Widerspruch zu den Go-Regeln für exportierte Feldnamen stehen.
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
name string
password string
preferredFish []string
createdAt time.Time
}
func main() {
u := &User{
name: "Sammy the Shark",
password: "fisharegreat",
createdAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Dies wird die folgende Ausgabe präsentieren:
Output{}
In dieser Version haben wir die Namen der Felder geändert, die mit Kamelen versehen werden sollen. Jetzt istNamename,Password istpassword und schließlich istCreatedAtcreatedAt. Innerhalb vonmain haben wir die Instanziierung unserer Struktur geändert, um diese neuen Namen zu verwenden. Wir übergeben die Struktur dann wie zuvor an die Funktionjson.MarshalIndent. Die Ausgabe ist diesmal ein leeres JSON-Objekt,{}.
Kamelhüllenfelder erfordern, dass das erste Zeichen in Kleinbuchstaben geschrieben ist. Während es JSON egal ist, wie Sie Ihre Felder benennen, ist es Go egal, da es die Sichtbarkeit des Felds außerhalb des Pakets anzeigt. Da dasencoding/json-Paket ein separates Paket von dem von uns verwendetenmain-Paket ist, müssen wir das erste Zeichen in Großbuchstaben schreiben, um es fürencoding/json sichtbar zu machen. Anscheinend sind wir in einer Sackgasse und müssen dem JSON-Encoder irgendwie mitteilen, wie dieses Feld benannt werden soll.
Verwenden von Struct-Tags zur Steuerung der Codierung
Sie können das vorherige Beispiel ändern, um exportierte Felder zu erhalten, die ordnungsgemäß mit Feldnamen in Kamelform codiert sind, indem Sie jedes Feld mit einem Struktur-Tag versehen. Das vonencoding/json erkannte Struktur-Tag hat einen Schlüssel vonjson und einen Wert, der die Ausgabe steuert. Durch Platzieren der Version der Feldnamen in Kamelhülle als Wert für den Schlüsseljsonverwendet der Encoder stattdessen diesen Namen. Dieses Beispiel behebt die beiden vorherigen Versuche:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish []string `json:"preferredFish"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Dies wird Folgendes ausgeben:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"preferredFish": null,
"createdAt": "2019-09-23T18:16:17.57739-04:00"
}
Wir haben die Feldnamen so geändert, dass sie für andere Pakete sichtbar sind, indem wir die ersten Buchstaben ihrer Namen groß geschrieben haben. Dieses Mal haben wir jedoch Struktur-Tags in Form vonjson:"name" hinzugefügt, wobei"name" der Name war, denjson.MarshalIndent beim Drucken unserer Struktur als JSON verwenden sollte.
Wir haben unseren JSON jetzt erfolgreich richtig formatiert. Beachten Sie jedoch, dass die Felder für einige Werte gedruckt wurden, obwohl wir diese Werte nicht festgelegt haben. Der JSON-Encoder kann diese Felder auch entfernen, wenn Sie möchten.
Leere JSON-Felder entfernen
In den meisten Fällen möchten wir die Ausgabe von Feldern unterdrücken, die in JSON nicht festgelegt sind. Da alle Typen in Go einen „Nullwert“ haben, einen Standardwert, auf den sie festgelegt sind, benötigt das Paketencoding/jsonzusätzliche Informationen, um erkennen zu können, dass ein Feld als nicht gesetzt betrachtet werden sollte, wenn es diesen Nullwert annimmt. Innerhalb des Werteteils eines beliebigenjson-Struktur-Tags können Sie den gewünschten Namen Ihres Feldes mit,omitempty versehen, um den JSON-Encoder anzuweisen, die Ausgabe dieses Felds zu unterdrücken, wenn das Feld auf den Wert Null gesetzt ist . Das folgende Beispiel behebt, dass in den vorherigen Beispielen keine leeren Felder mehr ausgegeben werden:
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish []string `json:"preferredFish,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Dieses Beispiel gibt Folgendes aus:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"createdAt": "2019-09-23T18:21:53.863846-04:00"
}
Wir haben die vorherigen Beispiele so geändert, dass das FeldPreferredFishjetzt das Struktur-Tagjson:"preferredFish,omitempty" enthält. Das Vorhandensein der Erweiterung von,omitemptyführt dazu, dass der JSON-Encoder dieses Feld überspringt, da wir beschlossen haben, es nicht gesetzt zu lassen. Dies hatte den Wertnull in den Ausgaben unserer vorherigen Beispiele.
Diese Ausgabe sieht viel besser aus, aber wir drucken immer noch das Passwort des Benutzers aus. Das Paketencoding/jsonbietet eine weitere Möglichkeit, private Felder vollständig zu ignorieren.
Private Felder ignorieren
Einige Felder müssen aus Strukturen exportiert werden, damit andere Pakete korrekt mit dem Typ interagieren können. Die Art dieser Felder kann jedoch kritisch sein. Unter diesen Umständen möchten wir, dass der JSON-Encoder das Feld vollständig ignoriert - auch wenn es festgelegt ist. Dies erfolgt unter Verwendung des speziellen Werts- als Wertargument für das Struktur-Tagjson:.
In diesem Beispiel wird das Problem behoben, dass das Kennwort des Benutzers angezeigt wird.
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
)
type User struct {
Name string `json:"name"`
Password string `json:"-"`
CreatedAt time.Time `json:"createdAt"`
}
func main() {
u := &User{
Name: "Sammy the Shark",
Password: "fisharegreat",
CreatedAt: time.Now(),
}
out, err := json.MarshalIndent(u, "", " ")
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println(string(out))
}
Wenn Sie dieses Beispiel ausführen, wird folgende Ausgabe angezeigt:
Output{
"name": "Sammy the Shark",
"createdAt": "2019-09-23T16:08:21.124481-04:00"
}
Das einzige, was wir in diesem Beispiel gegenüber den vorherigen geändert haben, ist, dass das Kennwortfeld jetzt den speziellen"-"-Wert für dasjson:-Struktur-Tag verwendet. Wir sehen, dass in der Ausgabe dieses Beispiels das Feldpassword nicht mehr vorhanden ist.
Diese Funktionen des Paketsencoding/json,,omitempty und"-", sind keine Standards. Was ein Paket mit den Werten eines struct-Tags anfängt, hängt von seiner Implementierung ab. Da das Paketencoding/jsonTeil der Standardbibliothek ist, haben auch andere Pakete diese Funktionen auf herkömmliche Weise implementiert. Es ist jedoch wichtig, die Dokumentation für Pakete von Drittanbietern zu lesen, die Struktur-Tags verwenden, um zu erfahren, was unterstützt wird und was nicht.
Fazit
Struct-Tags bieten eine leistungsstarke Möglichkeit, die Funktionalität von Code zu erweitern, der mit Ihren Structs zusammenarbeitet. Viele Standardbibliothekspakete und Pakete von Drittanbietern bieten Möglichkeiten zum Anpassen ihres Betriebs mithilfe von Struktur-Tags. Die effektive Verwendung in Ihrem Code bietet sowohl dieses Anpassungsverhalten als auch eine kurze Beschreibung der Verwendung dieser Felder für zukünftige Entwickler.