Golang --- reflect 帶我走的地獄路
Written on October 18th, 2017 by Richard Lin需求
原本公司load config的方法為使用yaml檔,配合官方套件輕鬆載入。
然而新的需求是希望能夠直接從enviroment variable中直接拿到config的值
於是一趟reflect的地獄巡禮即將展開。
Reflect這東西
由於需求所需要支援的型態各式各樣,除了基本int
float
string
bool
,甚至有slice
跟struct
,還有讓我在地獄走兩回的slice struct
。
因為傳進來的型態不固定,因此最外面的接口必須使用interface{}
來當作parameter。
再使用reflect取得值的型態跟賦予值。
reflect.Value
是變數的值,可以get或是set值
reflect.Type
是變數的型態,可以get到型態的各種資訊
開工
1. 首先我們需一個func load(out reflect.Value)
來處理外部來的資訊。
func load(out reflect.Value){
//check 有多少值在out
}
Notice :
out reflect.Value
該怎麼得到reflect.Value這型態
假設我們從外部拿到in interface{}
我們需要做的是reflect.ValueOf(in)
但in
因為要求關係會是pointer,後面的處理會無法處理,因此我們要再加上Elem()
來取得pointer所指向的值
結論:
reflect.ValueOf(in).Elem()
2. 我們可以使用out.NumField()
來知道有多少的值在out裡。
Example :
var number int
reflect.ValueOf(number).NumberField() = 1
type Mystruct struct{
val1 int
val2 string
}
reflect.ValueOf(Mystruct).NumberField() = 2
3. 把check完的值準備set值進去。
這時候處理的型態有可能為int
float
string
bool
slice
struct
(slice struct
為slice)
所有的基本型態都有語法可以輸入,例如string有reflect.Value.SetString(string)
麻煩的是struct與slice
struct slice
struct的處理方法,我是將他再塞進load(out reflect.Value)
裡,讓他再解析一次
slice的基本型態我是將值先使用json.Unmarshal([]byte(value), &slice)
再使用out.Set(reflect.ValueOf(slice))
將值塞入
但如果是slice struct
頭就大了
4. Slice Struct
主要做法為使用slice := reflect.MakeSlice(out.Type(), out.Len(), out.Cap())
來創出一個slice
再想辦法把值塞進slice裡,再講slice塞進out
func readSlice(out reflect.Value, outField reflect.StructField) error {
env := os.Getenv(outField.Tag.Get("env"))
tokens := getTokens(env)
elType := out.Type().Elem()
slice := reflect.MakeSlice(out.Type(), out.Len(), out.Cap())
if len(tokens) >= 1 {
for _, ele := range tokens {
el := reflect.New(elType).Elem()
ele = strings.TrimLeft(ele, "{")
ele = strings.TrimRight(ele, "}")
vals := strings.Split(ele, ",")
for index, val := range vals {
if index >= el.NumField() {
return fmt.Errorf("too many arguments. in %s", ele)
}
if err := setValue(el.Field(index), val); err != nil {
return err
}
}
slice = reflect.Append(slice, el)
}
}
out.Set(slice)
return nil
}