热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go每日一库之cast

今天我们再来介绍spf13大神的另一个库最初开发先安装:

简介

今天我们再来介绍 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
}

转换为切片

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

我们主要分析两个实现: 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  
 

根据传入参数的类型:

  • 如果是 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】,共同学习,一起进步~

Go 每日一库之 cast

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


以上所述就是小编给大家介绍的《Go 每日一库之 cast》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 我们 的支持!


推荐阅读
  • 投融资周报 | Circle 达成 4 亿美元融资协议,唯一艺术平台 A 轮融资超千万美元 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 文章目录Golang定时器Timer和Tickertime.Timertime.NewTimer()实例time.AfterFunctime.Tickertime.NewTicke ... [详细]
  • 在HTML布局中,即使将 `top: 0%` 和 `left: 0%` 设置为元素的定位属性,浏览器中仍然会出现空白填充。这个问题通常与默认的浏览器样式、盒模型或父元素的定位方式有关。为了消除这些空白,可以考虑重置浏览器的默认样式,确保父元素的定位方式正确,并检查是否有其他CSS规则影响了元素的位置。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • 数字图书馆近期展出了一批精选的Linux经典著作,这些书籍虽然部分较为陈旧,但依然具有重要的参考价值。如需转载相关内容,请务必注明来源:小文论坛(http://www.xiaowenbbs.com)。 ... [详细]
  • C++ 开发实战:实用技巧与经验分享
    C++ 开发实战:实用技巧与经验分享 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • Golomb 编码是一种高效的变长编码技术,专门用于整数的压缩。该方法通过预定义的参数 \( M \) 将输入整数分解为商 \( q \) 和余数 \( r \) 两部分。具体而言,输入整数除以 \( M \) 得到商 \( q \) 和余数 \( r \),其中商 \( q \) 采用一元编码表示,而余数 \( r \) 则使用二进制编码。这种编码方式在数据压缩和信息传输中具有显著的优势,特别是在处理具有特定概率分布的数据时表现出色。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
author-avatar
耗子很傻爱钻洞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有