紹介
構造体、またはstructsは、複数の情報を1つのユニットにまとめるために使用されます。これらの情報のまとまりは、Street
、City
、State
、およびPostalCode
から構成されるAddress
などの高度な概念を記述するために使用されます。データベースやAPIなどのシステムからこの情報を読み取るとき、struct tagsを使用して、この情報が構造体のフィールドにどのように割り当てられるかを制御できます。構造体のフィールドにアタッチされた小さなメタデータのピースである構造体タグは、構造体と連動する他のGoコードに指示を提供します。
構造体タグの形式は?
Goの構造体タグは、Goの構造体宣言の型の後に現れる注釈です。各タグは、対応する値に関連付けられた短い文字列から構成されています。
A struct tag looks like this, with the tag offset with backtick `
characters:
他のGoコードは、これらの構造体を調べ、リクエストされた特定のキーに割り当てられた値を抽出することができます。構造体タグは、それらを調べる追加のコードなしでは、コードの動作に影響を与えません。
次の例を試して、structタグがどのように見えるか、そして他のパッケージのコードがない場合、その効果がないことを確認してください。
これは次の出力になります:
OutputHi! My name is Sammy
この例では、User
型を定義し、Name
フィールドを持っています。 Name
フィールドにはexample:"name"
という構造体タグが付けられています。この特定のタグを会話で「example構造体タグ」と呼びます。なぜなら、それがキーとして「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
パッケージにあります。
structタグを使用せずにJSONがエンコードされる方法を確認するには、次の例を試してください:
これにより、次の出力が表示されます:
Output{
"Name": "Sammy the Shark",
"Password": "fisharegreat",
"CreatedAt": "2019-09-23T15:50:01.203059-04:00"
}
structを定義し、ユーザーの名前、パスワード、およびユーザーが作成された時間などのフィールドを含むユーザーを表すstruct
を定義しました。 main
関数内で、このユーザーのインスタンスを作成しました。すべてのフィールドに値を指定しましたが、PreferredFish
(Sammyはすべての魚が好きです)を除きました。その後、User
のインスタンスをjson.MarshalIndent
関数に渡しました。これは、外部のフォーマットツールを使用せずにJSON出力をより簡単に見るために使用されます。この呼び出しは、追加の空白を使用せずにJSONを印刷するためにjson.Marshal(u)
で置き換えることができます。 json.MarshalIndent
への2つの追加の引数は、出力へのプレフィックスを制御します(ここでは空の文字列で省略しています)、およびインデントに使用する文字を制御します。ここでは、2つのスペース文字が使用されます。json.MarshalIndent
から返された[]byte
をstring
にキャストし、その結果の文字列を端末に印刷するためにfmt.Println
に渡しました。
構造体のフィールドは、そのままの名前で表示されます。ただし、これは、フィールドの名前にキャメルケースを使用する通常のJSONスタイルではありません。次の例では、フィールドの名前をキャメルケーススタイルに変更します。この例を実行するとわかりますが、これは機能しません。なぜなら、希望するフィールド名がGoのエクスポートされたフィールド名に関するルールと競合するからです。
これにより、次の出力が表示されます:
Output{}
このバージョンでは、フィールドの名前をキャメルケースに変更しました。今では、Name
は name
、Password
は password
、そして最後に CreatedAt
は createdAt
です。 main
の本文では、新しい名前を使用して構造体のインスタンス化を変更しました。その後、構造体を json.MarshalIndent
関数に渡します。今回の出力は、空のJSONオブジェクト、{}
です。
フィールドをキャメルケースで適切に指定するには、最初の文字を小文字にする必要があります。JSONはフィールドの名前をどのように指定しても構いませんが、Goは構います。なぜなら、それはフィールドがパッケージ外でどのように見えるかを示しているからです。使用している encoding/json
パッケージは、使用している main
パッケージとは別のパッケージですので、encoding/json
に表示されるように最初の文字を大文字にする必要があります。行き詰まっているようです。JSONエンコーダーに、このフィールドをどのように名前付けしたいかを伝える方法が必要です。
エンコードを制御するための構造体タグの使用
前の例を修正して、各フィールドが正しくキャメルケースのフィールド名でエンコードされるように変更できます。各フィールドには、構造体タグを付けることで実現できます。encoding/json
が認識する構造体タグには、json
というキーを持つ値があり、その値が出力を制御します。フィールド名のキャメルケースバージョンをjson
キーの値として指定することで、エンコーダはその名前を使用します。この例は前の2つの試みを修正しています:
これは出力します:
Output{
"name": "Sammy the Shark",
"password": "fisharegreat",
"preferredFish": null,
"createdAt": "2019-09-23T18:16:17.57739-04:00"
}
前の2つの試みとは異なり、構造体のフィールドを他のパッケージから見えるように戻しました。ただし、今回はjson:"name"
という形式の構造体タグを追加しました。ここで"name"
は、json.MarshalIndent
が構造体をJSONとして出力する際に使用する名前です。
これでJSONを正しくフォーマットできました。ただし、一部の値のフィールドが設定されていないにもかかわらず印刷されたことに注意してください。JSONエンコーダは、必要に応じてこれらのフィールドも削除できます。
空のJSONフィールドの削除
JSONで設定されていないフィールドの出力を抑制することは一般的です。 Goのすべての型には「ゼロ値」と呼ばれる、いくつかのデフォルト値があり、encoding/json
パッケージはこのゼロ値を仮定するときに、フィールドが未設定であると判断するための追加情報が必要です。任意のjson
構造タグの値部分で、フィールドの望ましい名前の後に,omitempty
を付け加えることで、このフィールドがゼロ値に設定されている場合にそのフィールドの出力を抑制するようにJSONエンコーダに指示できます。次の例は、以前の例の空のフィールドを出力しないように修正しています:
この例は次のように出力されます:
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:
構造体タグの値引数として特別な値 -
を使用して行われます。
この例は、ユーザーのパスワードを公開する問題を修正しています。
この例を実行すると、次の出力が表示されます:
Output{
"name": "Sammy the Shark",
"createdAt": "2019-09-23T16:08:21.124481-04:00"
}
この例では、以前の例と比較して変更したのは、パスワードフィールドが今回、json:
構造体タグの値として特別な "-"
を使用していることだけです。この例の出力では、password
フィールドがもはや存在しないことがわかります。
encoding/json
パッケージのこれらの機能 — ,omitempty
、"-"
、および その他のオプション — は標準ではありません。構造体タグの値に関してパッケージが何をするかは、その実装に依存します。標準ライブラリの一部である encoding/json
パッケージは、他のパッケージが同じようにこれらの機能を実装することを慣例として採用しています。ただし、構造体タグを使用するサードパーティパッケージのドキュメントを読むことは重要です。何がサポートされているか、何がされていないかを学ぶために。
結論
構造体タグは、構造体を操作するコードの機能を拡張する強力な手段を提供します。多くの標準ライブラリやサードパーティのパッケージが、構造体タグを使用して操作をカスタマイズする方法を提供しています。コードで効果的に使用することで、このカスタマイズ動作を提供し、将来の開発者にこれらのフィールドがどのように使用されるかを簡潔に文書化することができます。
Source:
https://www.digitalocean.com/community/tutorials/how-to-use-struct-tags-in-go