Go 言語で struct を url.Values に展開した

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 というライブラリを読んで知りました。ソースコードがスッと読める言語は、こういう時に便利ですね。

github.com