前書き
構造体または構造体は、複数の情報を1つのユニットに収集するために使用されます。 これらのcollections of informationは、Street
、City
、State
、およびPostalCode
で構成されるAddress
などの高レベルの概念を説明するために使用されます。 。 データベースやAPIなどのシステムからこの情報を読み取る場合、structタグを使用して、この情報をstructのフィールドに割り当てる方法を制御できます。 構造体タグは、構造体のフィールドに付加されるメタデータの小さな断片であり、構造体で動作する他のGoコードに指示を提供します。
Structタグはどのように見えますか?
Go構造体タグは、Go構造体宣言の型の後に表示される注釈です。 各タグは、対応する値に関連付けられた短い文字列で構成されます。
構造体タグは次のようになり、タグはバッククォート+\
+ `文字でオフセットされます。
type User struct {
Name string `example:"name"`
}
他のGoコードは、これらの構造体を調べて、要求する特定のキーに割り当てられた値を抽出できます。 Structタグは、コードを検査する他のコードがない限り、コードの動作に影響を与えません。
この例を試して、structタグがどのように見えるかを確認してください。また、別のパッケージのコードがなければ、効果はありません。
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)
}
これは出力されます:
OutputHi! My name is Sammy
この例では、Name
フィールドを持つUser
タイプを定義します。 Name
フィールドには、example:"name"
の構造体タグが与えられています。 会話でこの特定のタグを「example struct tag」と呼びます。これは、「example」という単語をキーとして使用するためです。 example
構造体タグのName
フィールドの値は"name"
です。 User
タイプでは、fmt.Stringer
インターフェイスに必要なString()
メソッドも定義します。 これは、型をfmt.Println
に渡すと自動的に呼び出され、適切にフォーマットされたバージョンの構造体を生成する機会を与えてくれます。
main
の本体内に、User
タイプの新しいインスタンスを作成し、それをfmt.Println
に渡します。 構造体にはstructタグが存在していましたが、このGoコードの操作には影響がないことがわかります。 structタグが存在しない場合、まったく同じように動作します。
構造体タグを使用して何かを達成するには、実行時に構造体を調べる他のGoコードを記述する必要があります。 標準ライブラリには、操作の一部としてstructタグを使用するパッケージがあります。 これらの中で最も人気のあるものはencoding/json
パッケージです。
JSONのエンコード
JavaScript Object Notation(JSON)は、さまざまな文字列キーで整理されたデータのコレクションをエンコードするためのテキスト形式です。 さまざまなプログラムでデータを通信するために一般的に使用されます。形式が非常に単純なので、多くの異なる言語でデコードできるライブラリが存在します。 以下はJSONの例です。
{
"language": "Go",
"mascot": "Gopher"
}
このJSONオブジェクトには、language
とmascot
の2つのキーが含まれています。 これらのキーに続いて、関連付けられた値があります。 ここで、language
キーの値はGo
であり、mascot
には値Gopher
が割り当てられています。
標準ライブラリのJSONエンコーダーは、JSON出力のフィールドに名前を付ける方法をエンコーダーに示す注釈としてstructタグを使用します。 これらのJSONエンコードおよびデコードメカニズムは、encoding/json
packageにあります。
structタグなしでJSONがどのようにエンコードされるかを確認するには、この例を試してください。
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))
}
これにより、次の出力が印刷されます。
Output{
"Name": "Sammy the Shark",
"Password": "fisharegreat",
"CreatedAt": "2019-09-23T15:50:01.203059-04:00"
}
名前、パスワード、ユーザーが作成された時刻などのフィールドを持つユーザーを記述する構造体を定義しました。 main
関数内で、PreferredFish
を除くすべてのフィールドに値を指定してこのユーザーのインスタンスを作成します(Sammyはすべての魚が好きです)。 次に、User
のインスタンスをjson.MarshalIndent
関数に渡しました。 これは、外部のフォーマットツールを使用せずにJSON出力をより簡単に確認できるようにするために使用されます。 この呼び出しをjson.Marshal(u)
に置き換えて、空白を追加せずにJSONを受け取ることができます。 json.MarshalIndent
への2つの追加引数は、出力のプレフィックス(空の文字列では省略)と、インデントに使用する文字(ここでは2つのスペース文字)を制御します。 json.MarshalIndent
から生成されたエラーはすべてログに記録され、プログラムはos.Exit(1)
を使用して終了します。 最後に、json.MarshalIndent
から返された[]byte
をstring
にキャストし、結果の文字列をfmt.Println
に渡して端末に出力します。
構造体のフィールドは、指定したとおりに表示されます。 これは、予想される典型的なJSONスタイルではなく、フィールドの名前にキャメルケースを使用します。 次の例では、フィールドの名前をラクダケーススタイルに合わせて変更します。 この例を実行するとわかるように、目的のフィールド名がエクスポートされたフィールド名に関するGoのルールと競合するため、これは機能しません。
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))
}
これにより、次の出力が表示されます。
Output{}
このバージョンでは、フィールドの名前をキャメルケースに変更しました。 これで、Name
はname
、Password
はpassword
、最後にCreatedAt
はcreatedAt
になります。 main
の本体内で、これらの新しい名前を使用するように構造体のインスタンス化を変更しました。 次に、前と同じように構造体をjson.MarshalIndent
関数に渡します。 今回の出力は、空のJSONオブジェクト{}
です。
キャメルケーシングフィールドでは、最初の文字を小文字にする必要があります。 JSONはフィールドの名前を気にしませんが、Goはパッケージ外のフィールドの可視性を示すので気にしません。 encoding/json
パッケージは、使用しているmain
パッケージとは別のパッケージであるため、encoding/json
に表示するには、最初の文字を大文字にする必要があります。 私たちは行き詰まっているように見えます。このフィールドに名前を付けたいことをJSONエンコーダーに伝える方法が必要です。
Structタグを使用してエンコードを制御する
前の例を修正して、各フィールドにstructタグで注釈を付けることにより、キャメルケースのフィールド名で適切にエンコードされたフィールドをエクスポートすることができます。 encoding/json
が認識する構造体タグには、json
のキーと、出力を制御する値があります。 キャメルケースバージョンのフィールド名をjson
キーの値として配置することにより、エンコーダーは代わりにその名前を使用します。 この例は、前の2つの試みを修正します。
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))
}
これは出力されます:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"preferredFish": null,
"createdAt": "2019-09-23T18:16:17.57739-04:00"
}
名前の最初の文字を大文字にすることで、フィールド名を他のパッケージに表示されるように変更しました。 ただし、今回はjson:"name"
の形式で構造体タグを追加しました。"name"
は、構造体をJSONとして出力するときにjson.MarshalIndent
が使用する名前です。
JSONが正しくフォーマットされました。 ただし、一部の値のフィールドは、それらの値を設定していなくても印刷されていることに注意してください。 必要に応じて、JSONエンコーダーはこれらのフィールドも削除できます。
空のJSONフィールドを削除する
最も一般的には、JSONで設定されていないフィールドの出力を抑制します。 Goのすべての型には「ゼロ値」があり、それらが設定されているデフォルト値があるため、encoding/json
パッケージは、このゼロ値を想定するときに一部のフィールドが未設定と見なされる必要があることを通知できるように、追加情報が必要です。 json
構造体タグの値部分内で、フィールドの目的の名前に,omitempty
の接尾辞を付けて、フィールドがゼロ値に設定されているときにこのフィールドの出力を抑制するようにJSONエンコーダーに指示できます。 。 次の例は、前の例を修正して空のフィールドを出力しないようにします。
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))
}
この例は出力します:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"createdAt": "2019-09-23T18:21:53.863846-04:00"
}
前の例を変更して、PreferredFish
フィールドに構造体タグjson:"preferredFish,omitempty"
が含まれるようにしました。 ,omitempty
拡張が存在すると、JSONエンコーダーはそのフィールドをスキップします。これは、フィールドを未設定のままにすることにしたためです。 これは、前の例の出力では値null
でした。
この出力は見た目は格段に優れていますが、ユーザーのパスワードは引き続き印刷されています。 encoding/json
パッケージは、プライベートフィールドを完全に無視する別の方法を提供します。
プライベートフィールドを無視する
他のパッケージが型と正しく対話できるように、一部のフィールドは構造体からエクスポートする必要があります。 ただし、これらのフィールドの性質は機密性が高い場合があるため、このような状況では、JSONエンコーダーはフィールドが設定されている場合でもフィールドを完全に無視するようにします。 これは、json:
構造体タグへの値引数として特別な値-
を使用して行われます。
この例は、ユーザーのパスワードを公開する問題を修正します。
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))
}
この例を実行すると、次の出力が表示されます。
Output{
"name": "Sammy the Shark",
"createdAt": "2019-09-23T16:08:21.124481-04:00"
}
この例で前の例から変更したのは、パスワードフィールドがjson:
構造体タグに特別な"-"
値を使用するようになったことだけです。 この例の出力では、password
フィールドが存在しなくなっていることがわかります。
encoding/json
パッケージのこれらの機能、,omitempty
および"-"
は、標準ではありません。 パッケージがstructタグの値をどうするかは、その実装に依存します。 encoding/json
パッケージは標準ライブラリの一部であるため、他のパッケージも慣例と同じ方法でこれらの機能を実装しています。 ただし、structタグを使用するサードパーティパッケージのドキュメントを読んで、サポート対象とサポート対象を確認することが重要です。
結論
構造体タグは、構造体で機能するコードの機能を強化する強力な手段を提供します。 多くの標準ライブラリおよびサードパーティパッケージには、structタグを使用して操作をカスタマイズする方法が用意されています。 コードでこれらを効果的に使用すると、このカスタマイズ動作と、これらのフィールドが将来の開発者にどのように使用されるかを簡潔に文書化できます。