本文内容主要来自:https://darjun.github.io/2020/01/20/godailylib/cast/ cast 库 GitHub 地址:https://github.com/spf13/cast

Introduction

cast是一个小巧、实用的类型转换库,其作者是 spf13 大神,用于将一个类型转为另一个类型。
cast库能在几乎所有常见类型之间转换,使用非常方便。代码量也很小,有时间可以读读源码。
最初开发cast是用在 hugo 中的。

快速上手

安装:

  1. $ go get github.com/spf13/cast

使用:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/cast"
  5. )
  6. func main() {
  7. // ToString
  8. fmt.Println(cast.ToString("leedarjun")) // leedarjun
  9. fmt.Println(cast.ToString(8)) // 8
  10. fmt.Println(cast.ToString(8.31)) // 8.31
  11. fmt.Println(cast.ToString([]byte("one time"))) // one time
  12. fmt.Println(cast.ToString(nil)) // ""
  13. var foo interface{} = "one more time"
  14. fmt.Println(cast.ToString(foo)) // one more time
  15. // ToInt
  16. fmt.Println(cast.ToInt(8)) // 8
  17. fmt.Println(cast.ToInt(8.31)) // 8
  18. fmt.Println(cast.ToInt("8")) // 8
  19. fmt.Println(cast.ToInt(true)) // 1
  20. fmt.Println(cast.ToInt(false)) // 0
  21. var eight interface{} = 8
  22. fmt.Println(cast.ToInt(eight)) // 8
  23. fmt.Println(cast.ToInt(nil)) // 0
  24. }

cast实现了多种常见类型之间的相互转换,返回最符合直觉的结果。
例如:

  • nil转为string的结果为"",而不是"nil"
  • true转为string的结果为"true",而true转为int的结果为1
  • interface{}转为其他类型,要看它里面存储的值类型。

这些类型包括所有的基本类型(整形、浮点型、布尔值和字符串)、空接口、nil,时间(time.Time)、时长(time.Duration)以及它们的切片类型, 还有map[string]Type(其中Type为前面提到的类型):

  1. byte bool float32 float64 string
  2. int8 int16 int32 int64 int
  3. uint8 uint16 uint32 uint64 uint
  4. interface{} time.Time time.Duration nil

两组函数

cast提供了两组函数:

  • ToType(其中Type可以为任何支持的类型),将参数转换为Type类型。如果无法转换,返回Type类型的零值或nil
  • ToTypeE以 E 结尾,返回转换后的值和一个error。这组函数可以区分参数中实际存储了零值,还是转换失败了。

实现上大部分代码都类似,ToType在内部调用ToTypeE函数,返回结果并忽略错误。
ToType函数的实现在文件cast.go中, 而ToTypeE函数的实现在文件caste.go中。

  1. // cast/cast.go
  2. func ToBool(i interface{}) bool {
  3. v, _ := ToBoolE(i)
  4. return v
  5. }
  6. // ToDuration casts an interface to a time.Duration type.
  7. func ToDuration(i interface{}) time.Duration {
  8. v, _ := ToDurationE(i)
  9. return v
  10. }

ToTypeE函数都接受任意类型的参数(interface{}),然后使用类型断言根据具体的类型来执行不同的转换。如果无法转换,返回错误。

  1. // cast/caste.go
  2. func ToBoolE(i interface{}) (bool, error) {
  3. i = indirect(i)
  4. switch b := i.(type) {
  5. case bool:
  6. return b, nil
  7. case nil:
  8. return false, nil
  9. case int:
  10. if i.(int) != 0 {
  11. return true, nil
  12. }
  13. return false, nil
  14. case string:
  15. return strconv.ParseBool(i.(string))
  16. default:
  17. return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
  18. }
  19. }

首先调用indirect函数将参数中可能的指针去掉。如果类型本身不是指针,那么直接返回。否则返回指针指向的值。 循环直到返回一个非指针的值:

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

所以,下面代码输出都是 8:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/cast"
  5. )
  6. func main() {
  7. p := new(int)
  8. *p = 8
  9. fmt.Println(cast.ToInt(p)) // 8
  10. pp := &p
  11. fmt.Println(cast.ToInt(pp)) // 8
  12. }

高级转换

时间和时长转换

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

  1. func ToTimeE(i interface{}) (tim time.Time, err error) {
  2. i = indirect(i)
  3. switch v := i.(type) {
  4. case time.Time:
  5. return v, nil
  6. case string:
  7. return StringToDate(v)
  8. case int:
  9. return time.Unix(int64(v), 0), nil
  10. case int64:
  11. return time.Unix(v, 0), nil
  12. case int32:
  13. return time.Unix(int64(v), 0), nil
  14. case uint:
  15. return time.Unix(int64(v), 0), nil
  16. case uint64:
  17. return time.Unix(int64(v), 0), nil
  18. case uint32:
  19. return time.Unix(int64(v), 0), nil
  20. default:
  21. return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i)
  22. }
  23. }

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

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

字符串转换为时间:

  1. // cast/caste.go
  2. func StringToDate(s string) (time.Time, error) {
  3. return parseDateWith(s, []string{
  4. time.RFC3339,
  5. "2006-01-02T15:04:05", // iso8601 without timezone
  6. time.RFC1123Z,
  7. time.RFC1123,
  8. time.RFC822Z,
  9. time.RFC822,
  10. time.RFC850,
  11. time.ANSIC,
  12. time.UnixDate,
  13. time.RubyDate,
  14. "2006-01-02 15:04:05.999999999 -0700 MST", // Time.String()
  15. "2006-01-02",
  16. "02 Jan 2006",
  17. "2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon
  18. "2006-01-02 15:04:05 -07:00",
  19. "2006-01-02 15:04:05 -0700",
  20. "2006-01-02 15:04:05Z07:00", // RFC3339 without T
  21. "2006-01-02 15:04:05Z0700", // RFC3339 without T or timezone hh:mm colon
  22. "2006-01-02 15:04:05",
  23. time.Kitchen,
  24. time.Stamp,
  25. time.StampMilli,
  26. time.StampMicro,
  27. time.StampNano,
  28. })
  29. }
  30. func parseDateWith(s string, dates []string) (d time.Time, e error) {
  31. for _, dateType := range dates {
  32. if d, e = time.Parse(dateType, s); e == nil {
  33. return
  34. }
  35. }
  36. return d, fmt.Errorf("unable to parse date: %s", s)
  37. }

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

  1. // cast/caste.go
  2. func ToDurationE(i interface{}) (d time.Duration, err error) {
  3. i = indirect(i)
  4. switch s := i.(type) {
  5. case time.Duration:
  6. return s, nil
  7. case int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8:
  8. d = time.Duration(ToInt64(s))
  9. return
  10. case float32, float64:
  11. d = time.Duration(ToFloat64(s))
  12. return
  13. case string:
  14. if strings.ContainsAny(s, "nsuµmh") {
  15. d, err = time.ParseDuration(s)
  16. } else {
  17. d, err = time.ParseDuration(s + "ns")
  18. }
  19. return
  20. default:
  21. err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i)
  22. return
  23. }
  24. }

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

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

示例:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/spf13/cast"
  6. )
  7. func main() {
  8. now := time.Now()
  9. timestamp := 1579615973
  10. timeStr := "2020-01-21 22:13:48"
  11. fmt.Println(cast.ToTime(now)) // 2020-01-22 06:31:50.5068465 +0800 CST m=+0.000997701
  12. fmt.Println(cast.ToTime(timestamp)) // 2020-01-21 22:12:53 +0800 CST
  13. fmt.Println(cast.ToTime(timeStr)) // 2020-01-21 22:13:48 +0000 UTC
  14. d, _ := time.ParseDuration("1m30s")
  15. ns := 30000
  16. strWithUnit := "130s"
  17. strWithoutUnit := "130"
  18. fmt.Println(cast.ToDuration(d)) // 1m30s
  19. fmt.Println(cast.ToDuration(ns)) // 30µs
  20. fmt.Println(cast.ToDuration(strWithUnit)) // 2m10s
  21. fmt.Println(cast.ToDuration(strWithoutUnit)) // 130ns
  22. }

转换为切片

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

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

首先是ToIntSliceE

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

根据传入参数的类型:

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

ToStringSliceE

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

根据传入的参数类型:

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

示例:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/cast"
  5. )
  6. func main() {
  7. sliceOfInt := []int{1, 3, 7}
  8. arrayOfInt := [3]int{8, 12}
  9. // ToIntSlice
  10. fmt.Println(cast.ToIntSlice(sliceOfInt)) // [1 3 7]
  11. fmt.Println(cast.ToIntSlice(arrayOfInt)) // [8 12 0]
  12. sliceOfInterface := []interface{}{1, 2.0, "darjun"}
  13. sliceOfString := []string{"abc", "dj", "pipi"}
  14. stringFields := " abc def hij "
  15. any := interface{}(37)
  16. // ToStringSliceE
  17. fmt.Println(cast.ToStringSlice(sliceOfInterface)) // [1 2 darjun]
  18. fmt.Println(cast.ToStringSlice(sliceOfString)) // [abc dj pipi]
  19. fmt.Println(cast.ToStringSlice(stringFields)) // [abc def hij]
  20. fmt.Println(cast.ToStringSlice(any)) // [37]
  21. }

转为map[string]Type类型

cast库能将传入的参数转为map[string]Type类型,Type为上面支持的类型。

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

  1. func ToStringMapStringE(i interface{}) (map[string]string, error) {
  2. var m = map[string]string{}
  3. switch v := i.(type) {
  4. case map[string]string:
  5. return v, nil
  6. case map[string]interface{}:
  7. for k, val := range v {
  8. m[ToString(k)] = ToString(val)
  9. }
  10. return m, nil
  11. case map[interface{}]string:
  12. for k, val := range v {
  13. m[ToString(k)] = ToString(val)
  14. }
  15. return m, nil
  16. case map[interface{}]interface{}:
  17. for k, val := range v {
  18. m[ToString(k)] = ToString(val)
  19. }
  20. return m, nil
  21. case string:
  22. err := jsonStringToObject(v, &m)
  23. return m, err
  24. default:
  25. return m, fmt.Errorf("unable to cast %#v of type %T to map[string]string", i, i)
  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**,然后返回结果
  • 其他情况,返回错误。

示例:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/spf13/cast"
  5. )
  6. func main() {
  7. m1 := map[string]string{
  8. "name": "darjun",
  9. "job": "developer",
  10. }
  11. m2 := map[string]interface{}{
  12. "name": "jingwen",
  13. "age": 18,
  14. }
  15. m3 := map[interface{}]string{
  16. "name": "pipi",
  17. "job": "designer",
  18. }
  19. m4 := map[interface{}]interface{}{
  20. "name": "did",
  21. "age": 29,
  22. }
  23. jsonStr := `{"name":"bibi", "job":"manager"}`
  24. fmt.Println(cast.ToStringMapString(m1)) // map[job:developer name:darjun]
  25. fmt.Println(cast.ToStringMapString(m2)) // map[age:18 name:jingwen]
  26. fmt.Println(cast.ToStringMapString(m3)) // map[job:designer name:pipi]
  27. fmt.Println(cast.ToStringMapString(m4)) // map[job:designer name:pipi]
  28. fmt.Println(cast.ToStringMapString(jsonStr)) // map[job:manager name:bibi]
  29. }