Go
言語でWebアプリケーションを開発する際に、パスパラメータの値を構造体にparse
することはよくあることです。
例えば、下記のようなエンドポイントがあるとして、まずパスパラメータを受け取り、次にそれらのパラメータを構造体にparseしたいとします。
/firstname/{first_name}/lastname/{last_name}
パラメータの受け取りはgorilla/muxを使えば、できることが分かっています。
mux.Vars
関数を使えば、いい感じにmap
にして返してくれます。
今回お話ししたいのは、このmap
を構造体にparse
する方法についてです。
json
パッケージを利用してparse
する方法があるが・・・
このmap
の中身を構造体にparse
する方法も実はあって、json
パッケージを使えば簡単にできてしまします。
このソースは、The Go Playgroundでそのまま実行できます。
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
LastName string `json:"last_name"` // タグ名を json にする
FirstName string `json:"first_name"` // タグ名を json にする
}
func main() {
m := map[string]string{
"last_name": "Taro",
"first_name": "Suzuki",
}
p := Person{}
fmt.Printf("%+v\n", p)
parseParam(m, &p)
fmt.Printf("%+v\n", p)
}
// parseParam mapの値を構造体に parse する
func parseParam(m map[string]string, p interface{}) error {
bytes, err := json.Marshal(m)
if err != nil {
return err
}
return json.Unmarshal(bytes, p)
}
しかし、Person構造体のフィールドのタグにjson:"last_name"
とかjson:"first_name"
とか、json
と記載することに非常に抵抗感を感じました。
なぜなら、パラメータをjson
で受け取っているわけではないからです。
そこで、json
タグを使用せず独自のタグを設定してparse
できる方法を考えてみました。
reflect
機能を使って動的にparse
する
上の例と比べてみると、構造体のタグ名がjson
からparam
に変わったことにお気づきでしょうか。
結果parseParam
関数が長くなりますが、本来の目的に合ったタグ名を設定して目的を達成することができました。
reflect
機能を使うと、構造体のフィールドを動的に解析して好き放題にいじることができます。
コンパイル言語の安全性を損ねる恐れがあるのでreflect
の使いすぎは控えるべきですが、このような場面ではその威力を発揮してくれます。
このソースは、The Go Playgroundでそのまま実行できます。
package main
import (
"errors"
"fmt"
"reflect"
)
type Person struct {
LastName string `param:"last_name"` // タグ名を独自に設定できる
FirstName string `param:"first_name"` // タグ名を独自に設定できる
}
func main() {
m := map[string]string{
"last_name": "Taro",
"first_name": "Suzuki",
}
p := Person{}
fmt.Printf("%+v\n", p)
parseParam(m, &p)
fmt.Printf("%+v\n", p)
}
// parseParam mapの値を構造体に parse する
func parseParam(m map[string]string, p interface{}) error {
t := reflect.ValueOf(p).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := t.Type().Field(i).Tag.Get("param")
if tag == "" {
continue
}
value, ok := m[tag]
if !ok {
return errors.New("invalid value")
}
field.SetString(value)
}
return nil
}