

Go 每日一库之 cast
source link: https://studygolang.com/articles/26433
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简介
今天我们再来介绍 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 }
实际上, 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-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) }
时长类型的转换代码如下:
// 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 }
转换为切片
实际上,这些函数的实现基本类似。使用类型断言判断类型。如果就是要返回的类型,直接返回。否则根据类型进行相应的转换。
我们主要分析两个实现: 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) } }
根据传入参数的类型:
- 如果是
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: 本以为春节就这么几天,偷个懒,没想到。。。
参考
- cast GitHub 仓库
我
欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~
本文由博客一文多发平台 OpenWrite 发布!
Recommend
-
32
简介 今天我们来看一个很小,很实用的库 go-homedir 。顾名思义, go-homedir 用来获取用户的主目录。 实际上,使用标准库 os/user
-
46
简介 ini 是 Windows 上常用的配置文件格式。MySQL 的 Windows 版就是使用 ini 格式存储配置的。 go-ini 是 Go 语言中用于操作 ini 文件的第三方库。 本文...
-
23
简介 cobra 是一个命令行程序库,可以用来编写命令行程序。同时,它也提供了一个脚手架, 用于生成基于 cobra 的应用程序框架。非常多知名的开源项目使用了 cobra 库...
-
34
简介 上一篇文章介绍 cobra 的时候提到了 viper ,今天我们就来介绍一下这个库。 viper 是一...
-
29
简介 上一篇文章 Go 每日一库之 viper 中,我们介绍了 viper 可以监听文件修改进而自动重新加载。 其内部使用的就是 fsnotify 这...
-
39
简介 在日常开发中,日志是必不可少的功能。虽然有时可以用 fmt 库输出一些信息,但是灵活性不够。Go 标准库提供了一个日志库 log 。本文介绍 log 库的使用。 快速使用
-
23
简介 前一篇文章 介绍了 Go 标准库中的日志库 log 。最后我们也提到, log 库只提供了三组接口,功能过于简单了。 ...
-
42
简介 一线开发人员每天都要使用日期和时间相关的功能,各种定时器,活动时间处理等。标准库 time 使用起来不太灵活,特别是日期时间的创建和运算。
-
32
简介 程序中时常有发送邮件的需求。有异常情况了需要通知管理员和负责人,用户下单后可能需要通知订单信息,电商平台、中国移动和联通都有每月账单,这些都可以通过邮件来推送。还有我们平时收到的垃圾邮件大都也是通过这种方...
-
24
简介 今天我们来介绍 Go 语言的一个依赖注入(DI)库—— dig 。dig 是 uber 开源的库。Java 依赖注入的库有很多,相信即使不是做 Java 开发的童鞋也听过大名鼎鼎的 Spring。相比...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK