Golangにて遭遇したTipsを私的にメモする場所。
ちょいちょい加筆していきます。
構造体のタグを参照する
Json生成時に利用する構造体のタグ。
このタグを自由に使うための方法です(CSV等のカラム名等に便利)。
import (
"reflect"
)
// タグ付きで構造体を定義
type Record struct {
Name string `column:"name"`
Age int `column:"age"`
}
var csv Record = Record{
Name: "Hoge",
Age: 20,
}
// タグを1つずつ取得
t := reflect.TypeOf(csv)
fmt.Println(t.Field(0).Tag.Get("column")) //=> name
fmt.Println(t.Field(1).Tag.Get("column")) //=> age
// ループで参照
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i).Tag.Get("column")) //=> name -> age
}
// 元のフィールド名はこちら
for i := 0; i < t.NumField(); i++ {
fmt.Println(t.Field(i).Name) //=> NAME -> Age
}
構造体のフィールド名に変数の値を用いて代入する
構造体のフィールド名に対し、変数の値をフィールド名として用い代入する方法。
import (
"reflect"
)
type Record struct {
Name string
}
var record Record
// フィールド名
var fieldName string = "Name"
// ポインタへの変換必須
p := reflect.ValueOf(&record).Elem()
// reflectでフィールド名を指定して代入
// 但しSetStringは型が異なるとpanicを起こすので注意(よしなに処理するSetやSetInt等もある)
p.FieldByName(fieldName).SetString("hoge")
Mapのキーによるソートは一旦キーのみ配列化してソートしたほうが手軽
▼ Go: sort a Map by key #golang
https://gist.github.com/flaviocopes/bed52cc2ac407137b3931bc864b4e31b
なおsort.Slice()関数を使えばsortインタフェースを定義しなくてもソート可能。
こちらの記事が大変参考になりました。
▼ 今回はソート(sort)のお話。
http://text.baldanders.info/golang/sort/
JSONをパースして手っ取り早く値を参照する
例えば以下のJSONの場合。
{
"form1":{
"subject": "hoge"
"mailto": [
"hoge_1@example.com",
"hoge_2@example.com"
]
},
"form2":{
"subject": "fuga"
"mailto": [
"fuga_1@example.com",
"fuga_2@example.com"
]
},
}
普通は専用の構造体を用意して代入する。
でもいちいち構造体を用意するのは面倒だし、手っ取り早く値を参照したい場合がある。
そのような場合は一旦interface{}型でまるっと読み込んでしまうと便利。
import (
"encoding/json"
"io/ioutil"
)
bytes, err := ioutil.ReadFile(path_to_json)
var jsonData interface{}
err := json.Unmarshal(bytes, &jsonData)
これで全データがjsonDataにinterface{}型で格納されるが、
この方法で読み込むと全てmap[string]interface{}
型の階層構造になるのが難点。
そのままでは参照できないので、例えばform1
のsubject
を参照するのであれば、
jsonData.(map[string]interface{})["f1"].(map[string]interface{})["subject"]
のようにinterface{}型への型キャストを繰り返せば可能となる。
長くなるので良いとは言えないが、さくっと参照したい場合に便利です。
returnで構造体のリファレンスを返してメソッドチェーン
dbrでのサンプルを例に。
dbh.UpdateBySql("UPDATE〜").Exec()
- dbhはdbr内のあらゆるメソッドにアクセス可能な状態
- dbhを通じてupdate_builder.goに定義されているUpdateBySql()を実行
- update_builder.goにはUpdateBuilder型が定義されている
- UpdateBuilder型を用いたオブジェクトがreturn &UpdateBuilder{}で返る
- UpdateBuilder型には構造体メソッドExec()も定義されている
- つまりupdate_builder.go内のExec()が実行される
大雑多な解釈だが、
- UpdateBySql()の実行により「Update処理である」事を確定し
- Update処理に必要なデータをUpdateBuilder型に定義した上で
- update_builder.go内のExec()を実行させる事ができる
という事。
なるほどメソッドチェーンという感じでした。
インターフェース型を引数に渡す、返り値で受ける
▼ 引数に渡す
sortパッケージではLen()/Less()/Swap()の3メソッドを満たす型(構造体メソッドとして持つ型)を引数に渡すことで、内部でこのメソッドを呼んで結果を返してくれる。なので3つのメソッドを目的に応じて定義すれば、様々なsortパターンを実現することが可能になっている。
- sortパッケージ内にインターフェース型が定義されており
- インターフェースを満たす型を用意し(3つのメソッドさえ持っていれば独自に定義した型で良い)
- それをsortに渡す事で独自メソッドで処理された結果を得ることができる
▼ 返り値で受ける
dbrパッケージではSQLをExec()した場合、LastInsertId()/RowsAffected()メソッドを持つsql. Resultインターフェース型で結果が帰ってくる。なので返り値を受け取る変数をresultとした場合、result.RowsAffected()で更新された行数を得ることが可能になっている。
- dbrパッケージ内でsql.Resultインターフェース型を満たす型(2つのメソッドを持つ型)が用意され
- それをExec()メソッドの返り値として渡されて
- 実行元で実行可能にしている
つまりこれは、sortパッケージは内部処理を呼び出し元に委譲することで利用の幅(バリエーション)をもたせ、dbrパッケージでは実行されたメソッド(Select/Update/Delete...)別に結果をインターフェースのメソッドとして定義し、返すことで、一つの共通型でそれぞれの結果を得られる仕組みを提供している。
- sortは一つのメソッドの結果をユーザーが自由にカスタマイズ可能にするため
- dbrは異なるメソッドの結果を共通のメソッドで得るため
インターフェースも使い方で色々とできるんだな、と思ったのでメモ。
r.URLをstringとして処理したい
r.URLはtype *url.URL型
なので、そのままではstring型としての処理ができない。
その場合String()で参照できる。
strings.Replace(r.URL.String(), "www", "", 1)
実行時間の計測
start := time.Now()
end := time.Now()
result := end.Sub(start).Seconds()
空のmapを代入可能な状態で生成する
var var1 map[string]string = map[string]string{}
var1["hoge"] = "fuga" // OK
var var2 map[string]string
var2["hoge"] = "fuga" // NG(からデータがない状態でのこの方法はNG)
var2 = map[string]string{"hoge": "huga"} // OK
関数内では型推論による定義を行う。
hoge := map[string]string{}
hoge["fuga"] = "hogefuga" // OK
URL関連や環境変数の操作メモ
fmt.Println(os.Environ()) // 環境変数
fmt.Println(r.URL.Host) // hoge.fuga.com
fmt.Println(r.URL.Path) // /index.html
os.Setenv("HOGE", r.URL.Host) // 環境変数を設定
fmt.Println(os.Getenv("HOGE")) // 環境変数を参照
map[string]interface{}で定義された値の参照
例えば以下の定義からid
を参照しようとするとエラーが発生する。
hoge := map[string]interface{}{
"id": 1,
}
fmt.Println(hoge["id"]) // type interface {} does not support indexing
以下のエラーが発生する。
type interface {} does not support indexing
slice同士の連結
連結する側のslice末尾に「...」を忘れないこと。
Split等の関数実行時も「...」を付ければ連結できる。
result = append(result, source...)
result = append(result, strings.Split("1,2,3", ",")...)
strconv の Atoi と Itoa
返り値の個数が異るため無駄にはまる。
Atoiはerrorも受け取る必要があるので注意!
result, err := strconv.Atoi(target)
result := strconv.Itoa(target)
1文字単位に変換
string型は内部はsliceなのでruneで1=1文字単位で参照できる。
例えば先頭から10文字を取得する場合はこんな感じ。
text := string([]rune(text)[:10])
timeパッケージによる日時の取扱について
日時処理を行うtimeパッケージですが、独自の仕様満載で慣れるまで戸惑います。
とはいえ便利な機能も多々ありますので、備忘録としてまとめておきます。
ほんと色々な関数が用意されているので、一度ドキュメントに目を通すのがオススメです。
https://golang.org/pkg/time/
なおこちらの記事をとても参考にさせていただきました。
- Goで時刻を取り扱う
http://qiita.com/taizo/items/acbee530bd33c803dab4
ありがとうございます。
// 現在時刻から色々と参照(type Timeに色々と定義されている)
now := time.Now()
fmt.Println(now.Unix()) // 1486544245
fmt.Println(now.Month()) // 2
fmt.Println(reflect.TypeOf(now.Month())) // time.Month型(time.Dateに使える!)
// 任意の時刻を定義する
location, _ := time.LoadLocation("Asia/Tokyo")
var month time.Month = 2 // 定数の代わりに任意の数値で指定する場合はtime.Month型で定義
current := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), location) // 現在日時をセット
future := time.Date(2017, month, 9, 17, 0, 0, 0, location) // 未来の日時をセット
past := time.Date(2017, month, 7, 17, 0, 0, 0, location) // 過去の日時をセット
fmt.Println(current) // 現在日時(2017-02-08 17:59:11.942314121 +0900 JST)
// 自由なフォーマットに整形して出力
fmt.Println(now.Format("2006年01月02日 15時04分05秒")) // 2017年02月08日 21時20分21秒
fmt.Println(current.Format("2006年01月02日 15時04分05秒")) // 2017年02月08日 21時20分21秒
fmt.Println(future.Format("2006年01月02日 15時04分05秒")) // 2017年02月09日 17時00分00秒
fmt.Println(past.Format("2006年01月02日 15時04分05秒")) // 2017年02月07日 17時00分00秒
// 2つの時刻を比較する
fmt.Println(time.Since(past)) // pastから現在時刻までの経過時間(25h4m16.724052151s)
fmt.Println(reflect.TypeOf(time.Since(past))) // time.Duration型(const定義と同じ型!)
fmt.Println(future.After(past)) // futureはpastよりも未来か(true)
fmt.Println(past.After(future)) // pastはfutureよりも未来か(false)
// 定数に基準となる時刻単位がセットされている
fmt.Println(time.Hour) // 1h0m0s
fmt.Println(time.Minute) // 1m0s
fmt.Println(time.Second) // 1s
fmt.Println(reflect.TypeOf(time.Hour)) // time.Duration型(time.Sinceで取得する型と同じ!)
// 現在時刻に指定期間を加味した時刻を得る
fmt.Println(now.Add(time.Hour)) // 2017-02-08 19:24:39.432408145 +0900 JST(一時間後)
fmt.Println(now.Add(-time.Hour)) // 2017-02-08 19:24:39.432408145 +0900 JST(一時間前)
fmt.Println(now.Add((time.Since(past)))) // 2017-02-09 19:52:25.842717409 +0900 JST(time.Sinceで取得した時間経過分)
// 入力フォーマットを定義し「2014-12-31 00:00:00 +0000 UTC」(ISO-8601形式)で出力させる
var timeformat = "2006-01-02T15:04:05 MST" // 入力される日時のフォーマット定義(各値は固定値から選ぶ)
formatted, _ := time.Parse(timeformat, "2017-02-08T20:28:06 JST") // 右辺に変換対象となる日時パラメータ
fmt.Println(formatted) // 2017-02-08 20:28:06 +0900 JST
// 曜日名を得る
fmt.Println(time.Weekday(2)) // Tuesday
サンプル
指定日時が現在日時から7日以内か確認。
New!マーク等の表示に使えます。
func getNew() bool {
// 指定日時を分割
r := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z`)
date := r.FindStringSubmatch(varDate)
// int型へキャスト(この辺が融通きかない…)
yer, _ := strconv.Atoi(date[1])
num, _ := strconv.Atoi(date[2])
var mon time.Month = time.Month(num) // 月はtime.Month型なので注意!
day, _ := strconv.Atoi(date[3])
hur, _ := strconv.Atoi(date[4])
min, _ := strconv.Atoi(date[5])
sec, _ := strconv.Atoi(date[6])
// 7日前の日時をセット(実際はconstとか切ってね)
loc, _ := time.LoadLocation("Asia/Tokyo")
target := time.Date(yer, mon, day, hur, min, sec, 0, loc)
target = target.Add(24 * 7 * time.Hour)
// Before()でbool判定結果を返す
return time.Now().Before(target)
}
Regexで後方参照
\1や$1では簡単に取り出せない。
import "regexp"
...
r := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z`)
resultSlice := r.FindStringSubmatch(target)
fmt.Println(resultSlice) // [2016-02-07T10:36:25Z 2016 02 07 10 36 25]
連番付きフィールドからループで値を取得する
例えばこんな構造体。
type Hoge strunc {
Field1 string
Field2 string
...
}
通常フィールドへの参照は.
で行い、フィールド名に変数は利用できません。
JSのようにハッシュによる参照Hoge["Field" + i]
ができないため、golangでは困ってしまいます。
そんな時はreflect
パッケージで値を操作します。
package main
import (
"fmt"
"reflect"
"strconv"
)
type Hoge struct {
Field1 string
Field2 string
}
func main() {
result := []string{}
rv := reflect.ValueOf(Hoge{Field1: "value1", Field2: "value2"})
for i := 1; i <= 2; i++ {
label := "Field" + strconv.Itoa(i) // iをstring型に変換
st := rv.FieldByName(label) // reflect.Value型
result = append(result, st.Interface().(string)) // 一旦interface{}型に変換してからstring型へキャスト
}
fmt.Println(reflect.TypeOf(result[0])) // string
fmt.Println(result) // [value1 value2]
}
reflectで返される値はreflect.value型
になります。
reflect.value型はそのままstring型へキャストできないため、一旦interface{}型に変換してから型アサーションでstring型に変換します。
元データがgolang標準型であれば問題ないのですが、例えばDBから取得した値でsql.NullString
等を利用している場合、値が{{String:"Fuga", Valid: true}}
のようにネスト構造のため困ってしまいます。
そんな時はメソッドチェーンで解決できます。
st := rv.FieldByName(label).FieldByName("String")
Interfaceの理解に困ったら
sort.goのソースを読む。
https://golang.org/src/sort/sort.go
Len()
、Less()
、Swap()
の扱い方が非常に参考になりました。
処理タイプごとに構造体作って、構造体メソッドとしてLen()
、Less()
、Swap()
を定義しておく。つまりは独自構造体にも同様にメソッド追加しておけば独自のsort処理が作れるよーという感じ。
同一パッケージ内で同じメソッド名を同居させたい
基本ですが具体例で解説。
package models
type User struct { ... } // DB用の型
func (d *User) SelectById(id int) {
...
}
package models
type Book struct { ... } // DB用の型
func (d *Book) SelectById(id int) {
...
}
上記の例では同じパッケージ名package models
を利用している。
通常同一パッケージ内での同名メソッドは許可されないが、構造体メソッド化することで可能になる。これはDB操作など、一意のメソッド名で統一したい場合に便利。また構造体がベースなのでu.User.Hoge
のようにフィールドにもシームレスにアクセス可能。
つまりgolangの構造体はclass的振る舞いが可能ということ。
golangの柔軟性を垣間見た気がしました。
ちなみに同一パッケージ名利用のさらなる利点は、配置場所を一つのディレクトリでまかなえる点。
上記ではmodels
ディレクトリにuser.go
、book.go
を直接配置可能であり、importも***/models
の一回でまかなえます。これが異なるパッケージで独立していたらuser/user.go
、book/book.go
という感じになり、importも個別に行う必要があります。