Go 言語で struct を url.Values に展開した
現在勉強用に作っているボットでは、Slack の API を使います。勉強用なので、ライブラリを使わず、独自に実装しています。API ではエンドポイント毎にさまざまな要求パラメーターがあって、その引数を自由な形式にすると間違いのもとになるので、専用の struct を用意して間違いが起こらないようにしています。
func (a *API) ChatPostMessages(p ChatPostMessageParameters) (ChatPostMessagesResponse, error) { var r ChatPostMessagesResponse err := a.postAndStruct(&r, "/chat.postMessage", p) return r, err }
public にしているメソッドはそのようなキッチリ定義した struct を引数として要求する一方で、色々なエンドポイントを叩くときのメソッド (postAndStruct など) は共通化したかったため、private にしてあるその共通メソッドの引数では、どんな struct でも受けつけて url.Values に変換、POST や GET したいと思いました。
というわけで、あまり推奨されていないであろう、reflect で struct をなんでも展開という方法を使います。
func (a *API) buildParameters(parameters interface{}) url.Values { values := url.Values{} r := reflect.ValueOf(parameters) rt := r.Type() for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) key := "" if to := f.Tag.Get("json"); to != "" { key = to } else { key = strings.ToLower(f.Name) } v := fmt.Sprint(r.Field(i)) if v != "" { values.Add(key, v) } } values.Add("token", a.Token) return values }
interfrace{}
として受け取った値を reflect.ValueOf(parameters)
するのがキモで、それ以外は流れ作業でした。どうやれば interface{} -> struct できるかというのは github.com/fatih/structs というライブラリを読んで知りました。ソースコードがスッと読める言語は、こういう時に便利ですね。