【问题标题】:How to convert a string value to the correct reflect.Kind in go?如何将字符串值转换为正确的 reflect.Kind?
【发布时间】:2017-02-14 22:40:22
【问题描述】:

Go 中是否有通用的辅助方法可以根据 reflect.Kindstring 转换为正确的值?

还是需要我自己实现各种切换?

我有一个像“143”这样的字符串值和一个类型为“UInt16”的reflect.Value,并且喜欢转换该字符串值并将其设置为我的结构的UInt16值。

我当前的代码如下:

func setValueFromString(v reflect.Value, strVal string) error {
    switch v.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        val, err := strconv.ParseInt(strVal, 0, 64)
        if err != nil {
            return err
        }
        if v.OverflowInt(val) {
            return errors.New("Int value too big: " + strVal)
        }
        v.SetInt(val)
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        val, err := strconv.ParseUint(strVal, 0, 64)
        if err != nil {
            return err
        }
        if v.OverflowUint(val) {
            return errors.New("UInt value too big: " + strVal)
        }
        v.SetUint(val)
    case reflect.Float32:
        val, err := strconv.ParseFloat(strVal, 32)
        if err != nil {
            return err
        }
        v.SetFloat(val)
    case reflect.Float64:
        val, err := strconv.ParseFloat(strVal, 64)
        if err != nil {
            return err
        }
        v.SetFloat(val)
    case reflect.String:
        v.SetString(strVal)
    case reflect.Bool:
        val, err := strconv.ParseBool(strVal)
        if err != nil {
            return err
        }
        v.SetBool(val)
    default:
        return errors.New("Unsupported kind: " + v.Kind().String())
    }
    return nil
}

这已经可以了,但我想知道这是否已经在其他地方实现了。

【问题讨论】:

    标签: string go reflection type-conversion


    【解决方案1】:

    编辑:对原始问题的回答(“如何从 string 表示中获取 reflect.Kind”)在最后。对您编辑的问题的回答如下:


    您正在做的是最快和“最安全”的。如果您不想与那个大 switch 纠缠不清,您可以利用例如json 包已包含此 switch 以解码 JSON 字符串中的值(在 encoding/json/decode.go 中,未导出函数 literalStore())。

    您的解码函数可能如下所示:

    func Set(v interface{}, s string) error {
        return json.Unmarshal([]byte(s), v)
    }
    

    一个简单的电话json.Unmarshal()。使用/测试它:

    {
        var v int
        err := Set(&v, "1")
        fmt.Println(v, err)
    }
    {
        var v int
        err := Set(&v, "d")
        fmt.Println(v, err)
    }
    {
        var v uint32
        err := Set(&v, "3")
        fmt.Println(v, err)
    }
    {
        var v bool
        err := Set(&v, "true")
        fmt.Println(v, err)
    }
    {
        var v float32
        err := Set(&v, `5.1`)
        fmt.Println(v, err)
    }
    {
        var v string
        err := Set(&v, strconv.Quote("abc"))
        fmt.Println(v, err)
    }
    

    需要注意的一点:当你想传递string 时,必须引用它,例如与strconv.Quote()。输出(在Go Playground上试试):

    1 <nil>
    0 invalid character 'd' looking for beginning of value
    3 <nil>
    true <nil>
    5.1 <nil>
    abc <nil>
    

    如果您不想使用带引号的字符串(这只会使事情复杂化),您可以将其构建到 Set() 函数中:

    func Set(v interface{}, s string) error {
        if t := reflect.TypeOf(v); t.Kind() == reflect.Ptr &&
            t.Elem().Kind() == reflect.String {
            s = strconv.Quote(s)
        }
        return json.Unmarshal([]byte(s), v)
    }
    

    然后您可以使用string 变量的地址和未引用的string 值调用它:

    var v string
    err := Set(&v, "abc")
    fmt.Println(v, err)
    

    Go Playground 上试试这个变体。


    回答原问题:如何从string 表示中获取reflect.Kind

    reflect.Kind的声明:

    type Kind uint
    

    reflect.Kinds 的不同值是常量:

    const (
        Invalid Kind = iota
        Bool
        Int
        Int8
        // ...
        Struct
        UnsafePointer
    )
    

    reflect 包只为reflect.Kind() 类型提供了一个方法:

    func (k Kind) String() string
    

    就目前而言,您无法从其string 表示中获得reflect.Kind(使用Kind.String() 方法只能获得相反的方向)。但是提供这个功能并不难。

    我们要做的是从各种类型中构建一个map

    var strKindMap = map[string]reflect.Kind{}
    

    我们这样初始化它:

    func init() {
        for k := reflect.Invalid; k <= reflect.UnsafePointer; k++ {
            strKindMap[k.String()] = k
        }
    }
    

    这是可能且正确的,因为常量是使用 iota 初始化的,它计算为连续的无类型整数常量,第一个值为 reflect.Invalid,最后一个值为 reflect.UnsafePointer

    现在您可以通过简单地索引此地图来从其string 表示中获取reflect.Kind。执行此操作的辅助函数:

    func strToKind(s string) reflect.Kind {
        k, ok := strKindMap[s]
        if !ok {
            return reflect.Invalid
        }
        return k
    }
    

    我们完成了。测试/使用它:

    fmt.Printf("All: %#v\n", strKindMap)
    
    for _, v := range []string{"Hey", "uint8", "ptr", "func", "chan", "interface"} {
        fmt.Printf("String: %q, Kind: %v (%#v)\n", v, strToKind(v), strToKind(v))
    }
    

    输出(在Go Playground上试试):

    All: map[string]reflect.Kind{"int64":0x6, "uint8":0x8, "uint64":0xb, "slice":0x17, "uintptr":0xc, "int8":0x3, "array":0x11, "interface":0x14, "unsafe.Pointer":0x1a, "complex64":0xf, "complex128":0x10, "int":0x2, "uint":0x7, "int16":0x4, "uint16":0x9, "map":0x15, "bool":0x1, "int32":0x5, "ptr":0x16, "string":0x18, "func":0x13, "struct":0x19, "invalid":0x0, "uint32":0xa, "float32":0xd, "float64":0xe, "chan":0x12}
    String: "Hey", Kind: invalid (0x0)
    String: "uint8", Kind: uint8 (0x8)
    String: "ptr", Kind: ptr (0x16)
    String: "func", Kind: func (0x13)
    String: "chan", Kind: chan (0x12)
    String: "interface", Kind: interface (0x14)
    

    【讨论】:

    • 对不起,如果我不清楚。我也会编辑这个问题。我有一个像“143”这样的值作为字符串和一个reflect.Value,类型为“UInt16”,并且喜欢转换该字符串值并将其设置为我的结构的UInt16值
    猜你喜欢
    • 1970-01-01
    • 2013-07-13
    • 1970-01-01
    • 1970-01-01
    • 2020-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-10
    相关资源
    最近更新 更多