zoukankan      html  css  js  c++  java
  • Go 每日一库之 cast

    简介
    今天我们再来介绍 spf13 大神的另一个库cast。cast是一个小巧、实用的类型转换库,用于将一个类型转为另一个类型。最初开发cast是用在hugo中的。

    快速使用
    先安装:

    $ go get github.com/spf13/cast
    后使用:

    package main

    import (
    "fmt"

    "github.com/spf13/cast"
    )

    func main() {
    // ToString
    fmt.Println(cast.ToString("leedarjun")) // leedarjun
    fmt.Println(cast.ToString(8)) // 8
    fmt.Println(cast.ToString(8.31)) // 8.31
    fmt.Println(cast.ToString([]byte("one time"))) // one time
    fmt.Println(cast.ToString(nil)) // ""

    var foo interface{} = "one more time"
    fmt.Println(cast.ToString(foo)) // one more time

    // ToInt
    fmt.Println(cast.ToInt(8)) // 8
    fmt.Println(cast.ToInt(8.31)) // 8
    fmt.Println(cast.ToInt("8")) // 8
    fmt.Println(cast.ToInt(true)) // 1
    fmt.Println(cast.ToInt(false)) // 0

    var eight interface{} = 8
    fmt.Println(cast.ToInt(eight)) // 8
    fmt.Println(cast.ToInt(nil)) // 0
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    实际上,cast实现了多种常见类型之间的相互转换,返回最符合直觉的结果。例如:

    nil转为string的结果为"",而不是"nil";
    true转为string的结果为"true",而true转为int的结果为1;
    interface{}转为其他类型,要看它里面存储的值类型。
    这些类型包括所有的基本类型(整形、浮点型、布尔值和字符串)、空接口、nil,时间(time.Time)、时长(time.Duration)以及它们的切片类型,还有map[string]Type(其中Type为前面提到的类型):

    byte bool float32 float64 string
    int8 int16 int32 int64 int
    uint8 uint16 uint32 uint64 uint
    interface{} time.Time time.Duration nil
    1
    2
    3
    高级转换
    cast提供了两组函数:

    ToType(其中Type可以为任何支持的类型),将参数转换为Type类型。如果无法转换,返回Type类型的零值或nil;
    ToTypeE以 E 结尾,返回转换后的值和一个error。这组函数可以区分参数中实际存储了零值,还是转换失败了。
    实现上大部分代码都类似,ToType在内部调用ToTypeE函数,返回结果并忽略错误。ToType函数的实现在文件cast.go中,而ToTypeE函数的实现在文件caste.go中。

    // cast/cast.go
    func ToBool(i interface{}) bool {
    v, _ := ToBoolE(i)
    return v
    }

    // ToDuration casts an interface to a time.Duration type.
    func ToDuration(i interface{}) time.Duration {
    v, _ := ToDurationE(i)
    return v
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ToTypeE函数都接受任意类型的参数(interface{}),然后使用类型断言根据具体的类型来执行不同的转换。如果无法转换,返回错误。

    // cast/caste.go
    func ToBoolE(i interface{}) (bool, error) {
    i = indirect(i)

    switch b := i.(type) {
    case bool:
    return b, nil
    case nil:
    return false, nil
    case int:
    if i.(int) != 0 {
    return true, nil
    }
    return false, nil
    case string:
    return strconv.ParseBool(i.(string))
    default:
    return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    首先调用indirect函数将参数中可能的指针去掉。如果类型本身不是指针,那么直接返回。否则返回指针指向的值。循环直到返回一个非指针的值:

    // cast/caste.go
    func indirect(a interface{}) interface{} {
    if a == nil {
    return nil
    }
    if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
    // Avoid creating a reflect.Value if it's not a pointer.
    return a
    }
    v := reflect.ValueOf(a)
    for v.Kind() == reflect.Ptr && !v.IsNil() {
    v = v.Elem()
    }
    return v.Interface()
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    所以,下面代码输出都是 8:

    package main

    import (
    "fmt"

    "github.com/spf13/cast"
    )

    func main() {
    p := new(int)
    *p = 8
    fmt.Println(cast.ToInt(p)) // 8

    pp := &p
    fmt.Println(cast.ToInt(pp)) // 8
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    时间和时长转换
    时间类型的转换代码如下:

    func ToTimeE(i interface{}) (tim time.Time, err error) {
    i = indirect(i)

    switch v := i.(type) {
    case time.Time:
    return v, nil
    case string:
    return StringToDate(v)
    case int:
    return time.Unix(int64(v), 0), nil
    case int64:
    return time.Unix(v, 0), nil
    case int32:
    return time.Unix(int64(v), 0), nil
    case uint:
    return time.Unix(int64(v), 0), nil
    case uint64:
    return time.Unix(int64(v), 0), nil
    case uint32:
    return time.Unix(int64(v), 0), nil
    default:
    return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    根据传入的类型执行不同的处理:

    如果是time.Time,直接返回;
    如果是整型,将参数作为时间戳(自 UTC 时间1970.01.01 00:00:00到现在的秒数)调用time.Unix生成时间。Unix接受两个参数,第一个参数指定秒,第二个参数指定纳秒;
    如果是字符串,调用StringToDate函数依次尝试以下面这些时间格式调用time.Parse解析该字符串。如果某个格式解析成功,则返回获得的time.Time。否则解析失败,返回错误;
    其他任何类型都无法转换为time.Time。
    字符串转换为时间:

    // cast/caste.go
    func StringToDate(s string) (time.Time, error) {
    return parseDateWith(s, []string{
    time.RFC3339,
    "2006-01-02T15:04:05", // iso8601 without timezone
    time.RFC1123Z,
    time.RFC1123,
    time.RFC822Z,
    time.RFC822,
    time.RFC850,
    time.ANSIC,
    time.UnixDate,
    time.RubyDate,
    "2006-01-02 15:04:05.999999999 -0700 MST", // Time.String()
    "2006-01-02",
    "02 Jan 2006",
    "2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon
    "2006-01-02 15:04:05 -07:00",
    "2006-01-02 15:04:05 -0700",
    "2006-01-02 15:04:05Z07:00", // RFC3339 without T
    "2006-01-02 15:04:05Z0700", // RFC3339 without T or timezone hh:mm colon
    "2006-01-02 15:04:05",
    time.Kitchen,
    time.Stamp,
    time.StampMilli,
    time.StampMicro,
    time.StampNano,
    })
    }

    func parseDateWith(s string, dates []string) (d time.Time, e error) {
    for _, dateType := range dates {
    if d, e = time.Parse(dateType, s); e == nil {
    return
    }
    }
    return d, fmt.Errorf("unable to parse date: %s", s)
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    时长类型的转换代码如下:

    // cast/caste.go
    func ToDurationE(i interface{}) (d time.Duration, err error) {
    i = indirect(i)

    switch s := i.(type) {
    case time.Duration:
    return s, nil
    case int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8:
    d = time.Duration(ToInt64(s))
    return
    case float32, float64:
    d = time.Duration(ToFloat64(s))
    return
    case string:
    if strings.ContainsAny(s, "nsuµmh") {
    d, err = time.ParseDuration(s)
    } else {
    d, err = time.ParseDuration(s "ns")
    }
    return
    default:
    err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i)
    return
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    根据传入的类型进行不同的处理:

    如果是time.Duration类型,直接返回;
    如果是整型或浮点型,将其数值强制转换为time.Duration类型,单位默认为ns;
    如果是字符串,分为两种情况:如果字符串中有时间单位符号nsuµmh,直接调用time.ParseDuration解析;否则在字符串后拼接ns再调用time.ParseDuration解析;
    其他类型解析失败。
    示例:

    package main

    import (
    "fmt"
    "time"

    "github.com/spf13/cast"
    )

    func main() {
    now := time.Now()
    timestamp := 1579615973
    timeStr := "2020-01-21 22:13:48"

    fmt.Println(cast.ToTime(now)) // 2020-01-22 06:31:50.5068465 0800 CST m= 0.000997701
    fmt.Println(cast.ToTime(timestamp)) // 2020-01-21 22:12:53 0800 CST
    fmt.Println(cast.ToTime(timeStr)) // 2020-01-21 22:13:48 0000 UTC

    d, _ := time.ParseDuration("1m30s")
    ns := 30000
    strWithUnit := "130s"
    strWithoutUnit := "130"

    fmt.Println(cast.ToDuration(d)) // 1m30s
    fmt.Println(cast.ToDuration(ns)) // 30µs
    fmt.Println(cast.ToDuration(strWithUnit)) // 2m10s
    fmt.Println(cast.ToDuration(strWithoutUnit)) // 130ns
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    转换为切片
    实际上,这些函数的实现基本类似。使用类型断言判断类型。如果就是要返回的类型,直接返回。否则根据类型进行相应的转换。

    我们主要分析两个实现:ToIntSliceE和ToStringSliceE。ToBoolSliceE/ToDurationSliceE与ToIntSliceE基本相同。

    首先是ToIntSliceE:

    func ToIntSliceE(i interface{}) ([]int, error) {
    if i == nil {
    return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i)
    }

    switch v := i.(type) {
    case []int:
    return v, nil
    }

    kind := reflect.TypeOf(i).Kind()
    switch kind {
    case reflect.Slice, reflect.Array:
    s := reflect.ValueOf(i)
    a := make([]int, s.Len())
    for j := 0; j < s.Len(); j {
    val, err := ToIntE(s.Index(j).Interface())
    if err != nil {
    return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i)
    }
    a[j] = val
    }
    return a, nil
    default:
    return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    根据传入参数的类型:

    如果是nil,直接返回错误;
    如果是[]int,不用转换,直接返回;
    如果传入类型为切片或数组,新建一个[]int,将切片或数组中的每个元素转为int放到该[]int中。最后返回这个[]int;
    其他情况,不能转换。
    ToStringSliceE:

    func ToStringSliceE(i interface{}) ([]string, error) {
    var a []string

    switch v := i.(type) {
    case []interface{}:
    for _, u := range v {
    a = append(a, ToString(u))
    }
    return a, nil
    case []string:
    return v, nil
    case string:
    return strings.Fields(v), nil
    case interface{}:
    str, err := ToStringE(v)
    if err != nil {
    return a, fmt.Errorf("unable to cast %#v of type %T to []string", i, i)
    }
    return []string{str}, nil
    default:
    return a, fmt.Errorf("unable to cast %#v of type %T to []string", i, i)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    根据传入的参数类型:

    如果是[]interface{},将该参数中每个元素转为string,返回结果切片;
    如果是[]string,不需要转换,直接返回;
    如果是interface{},将参数转为string,返回只包含这个值的切片;
    如果是string,调用strings.Fields函数按空白符将参数拆分,返回拆分后的字符串切片;
    其他情况,不能转换。
    示例:

    package main

    import (
    "fmt"

    "github.com/spf13/cast"
    )

    func main() {
    sliceOfInt := []int{1, 3, 7}
    arrayOfInt := [3]int{8, 12}
    // ToIntSlice
    fmt.Println(cast.ToIntSlice(sliceOfInt)) // [1 3 7]
    fmt.Println(cast.ToIntSlice(arrayOfInt)) // [8 12 0]

    sliceOfInterface := []interface{}{1, 2.0, "darjun"}
    sliceOfString := []string{"abc", "dj", "pipi"}
    stringFields := " abc def hij "
    any := interface{}(37)
    // ToStringSliceE
    fmt.Println(cast.ToStringSlice(sliceOfInterface)) // [1 2 darjun]
    fmt.Println(cast.ToStringSlice(sliceOfString)) // [abc dj pipi]
    fmt.Println(cast.ToStringSlice(stringFields)) // [abc def hij]
    fmt.Println(cast.ToStringSlice(any)) // [37]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    转为map[string]Type类型
    cast库能将传入的参数转为map[string]Type类型,Type为上面支持的类型。

    其实只需要分析一个ToStringMapStringE函数就可以了,其他的实现基本一样。ToStringMapStringE返回map[string]string类型的值。

    func ToStringMapStringE(i interface{}) (map[string]string, error) {
    var m = map[string]string{}

    switch v := i.(type) {
    case map[string]string:
    return v, nil
    case map[string]interface{}:
    for k, val := range v {
    m[ToString(k)] = ToString(val)
    }
    return m, nil
    case map[interface{}]string:
    for k, val := range v {
    m[ToString(k)] = ToString(val)
    }
    return m, nil
    case map[interface{}]interface{}:
    for k, val := range v {
    m[ToString(k)] = ToString(val)
    }
    return m, nil
    case string:
    err := jsonStringToObject(v, &m)
    return m, err
    default:
    return m, fmt.Errorf("unable to cast %#v of type %T to map[string]string", i, i)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    根据传入的参数类型:

    如果是map[string]string,不用转换,直接返回;
    如果是map[string]interface{},将每个值转为string存入新的 map,最后返回新的 map;
    如果是map[interface{}]string,将每个键转为string存入新的 map,最后返回新的 map;
    如果是map[interface{}]interface{},将每个键和值都转为string存入新的 map,最后返回新的 map;
    如果是string类型,cast将它看成一个 JSON 串,解析这个 JSON 到map[string]string,然后返回结果;
    其他情况,返回错误。
    示例:

    package main

    import (
    "fmt"

    "github.com/spf13/cast"
    )

    func main() {
    m1 := map[string]string {
    "name": "darjun",
    "job": "developer",
    }

    m2 := map[string]interface{} {
    "name": "jingwen",
    "age": 18,
    }

    m3 := map[interface{}]string {
    "name": "pipi",
    "job": "designer",
    }

    m4 := map[interface{}]interface{} {
    "name": "did",
    "age": 29,
    }

    jsonStr := `{"name":"bibi", "job":"manager"}`

    fmt.Println(cast.ToStringMapString(m1)) // map[job:developer name:darjun]
    fmt.Println(cast.ToStringMapString(m2)) // map[age:18 name:jingwen]
    fmt.Println(cast.ToStringMapString(m3)) // map[job:designer name:pipi]
    fmt.Println(cast.ToStringMapString(m4)) // map[job:designer name:pipi]
    fmt.Println(cast.ToStringMapString(jsonStr)) // map[job:manager name:bibi]
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    总结
    cast库能在几乎所有常见类型之间转换,使用非常方便。代码量也很小,有时间建议读读源码。

    完整示例代码在GitHub上。

    ps: 本以为春节就这么几天,偷个懒,没想到。。。

    参考
    cast GitHub 仓库
    ————————————————
    版权声明:本文为CSDN博主「darjun」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/darjun/article/details/104173979

  • 相关阅读:
    Typora标题自动编号+设定快捷键技巧
    配置redis 4.0.11 集群
    学会使用 Mysql show processlist 排查问题
    Golang学习的方法和建议
    日志文件删除shell脚本
    运维趋势2019年总结,运维就是要做到"技多不压身"
    我的xshell配色方案,绿色/护眼/留存/备份
    对于api接口的爬虫,通常的解决方法
    maven 打包和构建的Linux命令(mvn)
    Istio的流量管理入门-charlieroro编写
  • 原文地址:https://www.cnblogs.com/ExMan/p/15563348.html
Copyright © 2011-2022 走看看