序章

Structures 、または structs は、複数の情報を1つのユニットにまとめるために使用されます。 これらの情報のコレクションは、次のような高レベルの概念を説明するために使用されます。 Address で構成されています Street, City, State、 と 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)
}

これは出力します:

Output
Hi! My name is Sammy

この例では、 User でタイプする Name 分野。 The Name フィールドには、の構造体タグが与えられています example:"name". 会話の中でこの特定のタグを「examplestructtag」と呼びます。これは、「example」という単語をキーとして使用しているためです。 The example structタグには値があります "name" のために Name 分野。 に User タイプ、私達はまた定義します String() によって必要とされる方法 fmt.Stringer インターフェース。 タイプをに渡すと、これは自動的に呼び出されます fmt.Println 構造体の適切にフォーマットされたバージョンを作成する機会を与えてくれます。

の体内 main、新しいインスタンスを作成します User 入力してに渡します fmt.Println. 構造体に構造体タグが存在していても、このGoコードの操作には影響がないことがわかります。 structタグが存在しない場合でも、まったく同じように動作します。

構造体タグを使用して何かを実行するには、実行時に構造体を調べるために他のGoコードを作成する必要があります。 標準ライブラリには、操作の一部として構造体タグを使用するパッケージがあります。 これらの中で最も人気があるのは encoding/json パッケージ。

JSONのエンコード

JavaScript Object Notation(JSON)は、さまざまな文字列キーで編成されたデータのコレクションをエンコードするためのテキスト形式です。 フォーマットは非常に単純であるため、多くの異なる言語でデータをデコードするためのライブラリが存在するため、さまざまなプログラム間でデータを通信するために一般的に使用されます。 以下はJSONの例です。

{
  "language": "Go",
  "mascot": "Gopher"
}

このJSONオブジェクトには2つのキーが含まれています。 languagemascot. これらのキーに続いて、関連する値があります。 ここに language キーの値は Gomascot 値が割り当てられます 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 (サミーはすべての魚が好きです)。 次に、のインスタンスを渡しました Userjson.MarshalIndent 関数。 これは、外部のフォーマットツールを使用せずにJSON出力をより簡単に確認できるようにするために使用されます。 この呼び出しは次のように置き換えることができます json.Marshal(u) 追加の空白なしでJSONを印刷します。 に2つの追加の引数 json.MarshalIndent 出力のプレフィックス(空の文字列では省略)と、インデントに使用する文字(ここでは2つのスペース文字)を制御します。 から生成されたエラー json.MarshalIndent ログに記録され、プログラムはを使用して終了します os.Exit(1). 最後に、 []byte から戻った json.MarshalIndentstring 結果の文字列をに渡しました 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
{}

このバージョンでは、キャメルケースになるフィールドの名前を変更しました。 今 Namename, Passwordpassword、 そして最後に CreatedAtcreatedAt. の体内 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.MarshalIndent 構造体をJSONとして出力するときに使用します。

これで、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 フィールドにstructタグが追加されました json:"preferredFish,omitempty". の存在 ,omitempty 拡張すると、JSONエンコーダーはそのフィールドをスキップします。これは、フィールドを未設定のままにすることにしたためです。 これには価値がありました null 前の例の出力で。

この出力ははるかに良く見えますが、まだユーザーのパスワードを出力しています。 The 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タグを使用して操作をカスタマイズする方法を提供します。 これらをコードで効果的に使用すると、このカスタマイズ動作が提供され、将来の開発者がこれらのフィールドをどのように使用するかが簡潔に文書化されます。