LoginSignup
1
0

More than 1 year has passed since last update.

Go言語でパスパラメータの値を構造体に自動代入させる

Posted at

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でそのまま実行できます。

json
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でそのまま実行できます。

reflect
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 
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0