Goで構造体タグを使用する方法

提供:Dev Guides
移動先:案内検索

序章

構造体または構造体は、複数の情報を1つのユニットにまとめて収集するために使用されます。 これらの情報のコレクションは、StreetCity、[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オブジェクトには、languagemascotの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から返された[]bytestringにキャストし、結果の文字列を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{}

このバージョンでは、キャメルケースになるフィールドの名前を変更しました。 これで、NamenamePasswordpassword、最後に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として出力するときに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タグを使用して操作をカスタマイズする方法を提供します。 これらをコードで効果的に使用すると、このカスタマイズ動作が提供され、将来の開発者がこれらのフィールドをどのように使用するかが簡潔に文書化されます。