$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Goでstruct内に定義されたタグの内容を再帰的に取得する

あるstruct構造の中のdbというタグの内容に何が含まれているかをすべて取得したい時があったため。

たとえばこういう構造があったとして

type Timestamp struct {
    CreatedAt time.Time `db:"created_at"`
    UpdatedAt time.Time `db:"updated_at"`
}
type JsonField struct {
    Foo string `json:"foo"`
    Bar string `json:"bar"`
}
type Post struct {
    ID       int64     `db:"id"`
    UserID   int64     `db:"user_id"`
    Body     string    `db:"body"`
    PostedAt time.Time `db:"posted_at"`
    Deleted  bool      `db:"deleted"`
    NoName   string    `db:"-"`
    JsonBody JsonField `db:"json_body"`
    Timestamp
}

dbタグに含まれる以下の文字列を全て取得したい。

id
user_id
body
posted_at
deleted
json_body
created_at
updated_at

reflectで頑張る

再帰的に取ってくるのをreflectを使って自分で頑張るとこんな感じ。

func main() {
    p := Post{}
    dbTags := extractDBTags(reflect.TypeOf(p))
    for _, tag := range dbTags {
        fmt.Println(tag)
    }
}

func extractDBTags(t reflect.Type) []string {
    if t.Kind() == reflect.Ptr {
        t = t.Elem()
    }

    dbTags := make([]string, 0)

    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)

        // Get the `db` tag if any
        dbTag, ok := f.Tag.Lookup("db")
        if ok && dbTag != "-" {
            dbTags = append(dbTags, dbTag)
        }

        // If this is an embedded struct, recurse
        if f.Type.Kind() == reflect.Struct {
            dbTags = append(dbTags, extractDBTags(f.Type)...)
            continue
        }
    }

    return dbTags
}

jmoiron/sqlx/reflectx を使う

https://pkg.go.dev/github.com/jmoiron/sqlx/reflectx を使うとちょっと簡単に実装できる。

func main() {
    m := reflectx.NewMapper("db")
    fields := m.TypeMap(reflect.TypeOf(p))
    for tagname, _ := range fields.Names {
        if !strings.Contains(tagname, ".") {
            fmt.Println(tagname)
        }
    }
}

このやり方の場合、if !strings.Contains(tagname, ".") { の条件がないと、json_body.Fooみたいなのも入ってきてしまうのが難しいところ。もしかしたらもっといい方法あるかも。