Goで構造体タグを使用する方法
序章
構造体または構造体は、複数の情報を1つのユニットにまとめて収集するために使用されます。 これらの情報のコレクションは、Street
、City
、[X147Xで構成されるAddress
などの高レベルの概念を説明するために使用されます。 ]、およびPostalCode
。 データベースやAPIなどのシステムからこの情報を読み取る場合、構造体タグを使用して、この情報を構造体のフィールドに割り当てる方法を制御できます。 構造体タグは、構造体のフィールドにアタッチされた小さなメタデータであり、構造体で機能する他のGoコードに指示を提供します。
構造体タグはどのように見えますか?
Go構造体タグは、Go構造体宣言の型の後に表示される注釈です。 各タグは、対応する値に関連付けられた短い文字列で構成されています。
構造体タグは次のようになり、タグはバックティック`
文字でオフセットされます。
type User struct { Name string `example:"name"` }
他のGoコードは、これらの構造体を調べて、要求する特定のキーに割り当てられた値を抽出することができます。 構造体タグは、それらを調べる他のコードがなければ、コードの操作に影響を与えません。
この例を試して、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"
の構造体タグが付けられています。 会話の中でこの特定のタグを「examplestructtag」と呼びます。これは、「example」という単語をキーとして使用しているためです。 example
構造体タグのName
フィールドの値は"name"
です。 User
タイプでは、fmt.Stringer
インターフェースに必要なString()
メソッドも定義します。 これは、型をfmt.Println
に渡すときに自動的に呼び出され、構造体の適切にフォーマットされたバージョンを生成する機会を与えてくれます。
main
の本体内に、User
タイプの新しいインスタンスを作成し、それをfmt.Println
に渡します。 構造体に構造体タグが存在していても、このGoコードの操作には影響がないことがわかります。 structタグが存在しない場合でも、まったく同じように動作します。
構造体タグを使用して何かを実行するには、実行時に構造体を調べるために他のGoコードを作成する必要があります。 標準ライブラリには、操作の一部として構造体タグを使用するパッケージがあります。 これらの中で最も人気があるのは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
パッケージにあります。
この例を試して、構造体タグなしで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タグで注釈を付けることにより、キャメルケースのフィールド名で適切にエンコードされたフィールドをエクスポートできます。 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タグを使用して操作をカスタマイズする方法を提供します。 これらをコードで効果的に使用すると、このカスタマイズ動作が提供され、将来の開発者がこれらのフィールドをどのように使用するかが簡潔に文書化されます。