分享
  1. 首页
  2. 文章

Go 每日一库之 cast

darjun · · 2743 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

简介

今天我们再来介绍 spf13 大神的另一个库castcast是一个小巧、实用的类型转换库,用于将一个类型转为另一个类型。
最初开发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
}

实际上,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

高级转换

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
}

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)
 }
}

首先调用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()
}

所以,下面代码输出都是 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
}

时间和时长转换

时间类型的转换代码如下:

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)
 }
}

根据传入的类型执行不同的处理:

  • 如果是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月02日T15: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月02日T15: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)
}

时长类型的转换代码如下:

// 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
 }
}

根据传入的类型进行不同的处理:

  • 如果是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
}

转换为切片

实际上,这些函数的实现基本类似。使用类型断言判断类型。如果就是要返回的类型,直接返回。否则根据类型进行相应的转换。

我们主要分析两个实现:ToIntSliceEToStringSliceEToBoolSliceE/ToDurationSliceEToIntSliceE基本相同。

首先是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)
 }
}

根据传入参数的类型:

  • 如果是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)
 }
}

根据传入的参数类型:

  • 如果是[]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]
}

转为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)
 }
}

根据传入的参数类型:

  • 如果是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]
}

总结

cast库能在几乎所有常见类型之间转换,使用非常方便。代码量也很小,有时间建议读读源码。

完整示例代码在GitHub上。

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

参考

  1. cast GitHub 仓库

我的博客

欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~

本文由博客一文多发平台 OpenWrite 发布!

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:darjun

查看原文:Go 每日一库之 cast

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2743 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏