Golang --- reflect 帶我走的地獄路

需求


原本公司load config的方法為使用yaml檔,配合官方套件輕鬆載入。
然而新的需求是希望能夠直接從enviroment variable中直接拿到config的值
於是一趟reflect的地獄巡禮即將展開。

Reflect這東西


由於需求所需要支援的型態各式各樣,除了基本int float string bool,甚至有slicestruct,還有讓我在地獄走兩回的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
}

github專案連結


comments powered by Disqus