南山小小糖 發表於 2025-11-21 10:00:22

Go语言类型转换工具库cast的实现

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">简介</a></li><li><a href="#_label1">快速入门</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">安装</a></li><li><a href="#_lab2_1_1">使用</a></li></ul><li><a href="#_label2">高级转换</a></li><ul class="second_class_ul"></ul><li><a href="#_label3">时间和时长转换</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_2">时间类型的转换</a></li><li><a href="#_lab2_3_3">字符串转换为时间</a></li><li><a href="#_lab2_3_4">时长类型的转换</a></li></ul><li><a href="#_label4">转换为切片</a></li><ul class="second_class_ul"><li><a href="#_lab2_4_5">ToIntSliceE</a></li><li><a href="#_lab2_4_6">ToStringSliceE</a></li></ul><li><a href="#_label5">转为mapType类型</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>简介</h2>
<p><code>cast</code>可以在 Go 中轻松安全地从一种类型转换为另一种类型,<code>cast</code> 提供了简单的函数来轻松地将数字转换为字符串,将接口转换为布尔值等。当需要显示类型转换时,<code>cast</code> 会智能地执行转换操作。</p>
<p class="maodian"><a name="_label1"></a></p><h2>快速入门</h2>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>安装</h3>
<div class="jb51code"><pre class="brush:go;">go get github.com/spf13/cast
</pre></div>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>使用</h3>
<div class="jb51code"><pre class="brush:go;">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
}
</pre></div>
<p>实际上,<code>cast</code>实现了多种常见类型之间的相互转换,返回最符合直觉的结果。例如:</p>
<ul><li><code>nil</code>转为<code>string</code>的结果为<code>&quot;&quot;</code>,而不是<code>&quot;nil&quot;</code>;</li><li><code>true</code>转为<code>string</code>的结果为<code>&quot;true&quot;</code>,而<code>true</code>转为<code>int</code>的结果为<code>1</code>;</li><li><code>interface{}</code>转为其他类型,要看它里面存储的值类型。</li></ul>
<p>这些类型包括所有的基本类型(整形、浮点型、布尔值和字符串)、空接口、<code>nil</code>,时间(<code>time.Time</code>)、时长(<code>time.Duration</code>)以及它们的切片类型,还有<code>mapType</code>(其中<code>Type</code>为前面提到的类型):</p>
<div class="jb51code"><pre class="brush:go;">byte   bool      float32    float64    string
int8   int16   int32      int64      int
uint8    uint16    uint32   uint64   uint
interface{}   time.Timetime.Duration   nil
</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>高级转换</h2>
<p><code>cast</code>提供了两组函数:</p>
<ul><li><code>ToType</code>(其中<code>Type</code>可以为任何支持的类型),将参数转换为<code>Type</code>类型。如果无法转换,返回<code>Type</code>类型的零值或<code>nil</code>;</li><li><code>ToTypeE</code>以 E 结尾,返回转换后的值和一个<code>error</code>。这组函数可以区分参数中实际存储了零值,还是转换失败了。</li></ul>
<p>实现上大部分代码都类似,<code>ToType</code>在内部调用<code>ToTypeE</code>函数,返回结果并忽略错误。<code>ToType</code>函数的实现在文件<code>cast.go</code>中,而<code>ToTypeE</code>函数的实现在文件<code>caste.go</code>中。</p>
<p>部分源码如下:</p>
<div class="jb51code"><pre class="brush:go;">// ToBoolE casts an interface to a bool type.
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
}
</pre></div>
<p><code>ToTypeE</code>函数都接受任意类型的参数(<code>interface{}</code>),然后使用类型断言根据具体的类型来执行不同的转换。如果无法转换,返回错误。</p>
<div class="jb51code"><pre class="brush:go;">// ToBoolE casts an interface to a bool type.
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))
        case json.Number:
                v, err := ToInt64E(b)
                if err == nil {
                        return v != 0, nil
                }
                return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
        default:
                return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i)
        }
}
</pre></div>
<p>首先调用<code>indirect</code>函数将参数中可能的指针去掉。如果类型本身不是指针,那么直接返回。否则返回指针指向的值。循环直到返回一个非指针的值:</p>
<div class="jb51code"><pre class="brush:go;">// From html/template/content.go
// Copyright 2011 The Go Authors. All rights reserved.
// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
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 &amp;&amp; !v.IsNil() {
                v = v.Elem()
        }
        return v.Interface()
}
</pre></div>
<p>所以,下面代码输出都是 8:</p>
<div class="jb51code"><pre class="brush:go;">package main

import (
    "fmt"

    "github.com/spf13/cast"
)

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

pp := &amp;p
fmt.Println(cast.ToInt(pp))// 8
}
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>时间和时长转换</h2>
<p class="maodian"><a name="_lab2_3_2"></a></p><h3>时间类型的转换</h3>
<p>源码如下:</p>
<div class="jb51code"><pre class="brush:go;">// ToTimeE casts an interface to a time.Time type.
func ToTimeE(i interface{}) (tim time.Time, err error) {
        return ToTimeInDefaultLocationE(i, time.UTC)
}

// ToTimeInDefaultLocationE casts an empty interface to time.Time,
// interpreting inputs without a timezone to be in the given location,
// or the local timezone if nil.
func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time.Time, err error) {
        i = indirect(i)

        switch v := i.(type) {
        case time.Time:
                return v, nil
        case string:
                return StringToDateInDefaultLocation(v, location)
        case json.Number:
                s, err1 := ToInt64E(v)
                if err1 != nil {
                        return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i)
                }
                return time.Unix(s, 0), nil
        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)
        }
}
</pre></div>
<p>根据传入的类型执行不同的处理:</p>
<ul><li>如果是<code>time.Time</code>,直接返回;</li><li>如果是整型,将参数作为时间戳(自 <code>UTC</code> 时间<code>1970.01.01 00:00:00</code>到现在的秒数)调用<code>time.Unix</code>生成时间。<code>Unix</code>接受两个参数,第一个参数指定秒,第二个参数指定纳秒;</li><li>如果是字符串,调用<code>StringToDate</code>函数依次尝试以下面这些时间格式调用<code>time.Parse</code>解析该字符串。如果某个格式解析成功,则返回获得的<code>time.Time</code>。否则解析失败,返回错误;</li><li>其他任何类型都无法转换为<code>time.Time</code>。</li></ul>
<p class="maodian"><a name="_lab2_3_3"></a></p><h3>字符串转换为时间</h3>
<p>源码:</p>
<div class="jb51code"><pre class="brush:go;">// 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)
}
</pre></div>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>时长类型的转换</h3>
<p>源码如下:</p>
<div class="jb51code"><pre class="brush:go;">// 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
}
}
</pre></div>
<p>根据传入的类型进行不同的处理:</p>
<ul><li>如果是<code>time.Duration</code>类型,直接返回;</li><li>如果是整型或浮点型,将其数值强制转换为<code>time.Duration</code>类型,单位默认为<code>ns</code>;</li><li>如果是字符串,分为两种情况:如果字符串中有时间单位符号<code>nsu&micro;mh</code>,直接调用<code>time.ParseDuration</code>解析;否则在字符串后拼接<code>ns</code>再调用time.ParseDuration解析;</li><li>其他类型解析失败。</li></ul>
<p>示例:</p>
<div class="jb51code"><pre class="brush:go;">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.50684650800 CST m= 0.000997701
fmt.Println(cast.ToTime(timestamp)) // 2020-01-21 22:12:530800 CST
fmt.Println(cast.ToTime(timeStr))   // 2020-01-21 22:13:480000 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
}
</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>转换为切片</h2>
<p>实际上,这些函数的实现基本类似。使用类型断言判断类型。如果就是要返回的类型,直接返回。否则根据类型进行相应的转换。</p>
<p>我们主要分析两个实现:<code>ToIntSliceE</code>和<code>ToStringSliceE</code>。<code>ToBoolSliceE/ToDurationSliceE</code>与<code>ToIntSliceE</code>基本相同。</p>
<p class="maodian"><a name="_lab2_4_5"></a></p><h3>ToIntSliceE</h3>
<p>源码如下:</p>
<div class="jb51code"><pre class="brush:go;">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 &lt; 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 = val
    }
    return a, nil
default:
    return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i)
}
}
</pre></div>
<p>根据传入参数的类型:</p>
<ul><li>如果是<code>nil</code>,直接返回错误;</li><li>如果是<code>[]int</code>,不用转换,直接返回;</li><li>如果传入类型为<strong>切片</strong>或<strong>数组</strong>,新建一个<code>[]int</code>,将切片或数组中的每个元素转为<code>int</code>放到该<code>[]int</code>中。最后返回这个<code>[]int</code>;</li><li>其他情况,不能转换。</li></ul>
<p class="maodian"><a name="_lab2_4_6"></a></p><h3>ToStringSliceE</h3>
<p>源码如下:</p>
<div class="jb51code"><pre class="brush:go;">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)
}
}
</pre></div>
<p>根据传入的参数类型:</p>
<ul><li>如果是<code>[]interface{}</code>,将该参数中每个元素转为<code>string</code>,返回结果切片;</li><li>如果是<code>[]string</code>,不需要转换,直接返回;</li><li>如果是<code>interface{}</code>,将参数转为<code>string</code>,返回只包含这个值的切片;</li><li>如果是<code>string</code>,调用<code>strings.Fields</code>函数按空白符将参数拆分,返回拆分后的字符串切片;</li><li>其他情况,不能转换。</li></ul>
<p>示例:</p>
<div class="jb51code"><pre class="brush:go;">package main

import (
"fmt"

"github.com/spf13/cast"
)

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

sliceOfInterface := []interface{}{1, 2.0, "darjun"}
sliceOfString := []string{"abc", "dj", "pipi"}
stringFields := " abcdef hij   "
any := interface{}(37)
// ToStringSliceE
fmt.Println(cast.ToStringSlice(sliceOfInterface))//
fmt.Println(cast.ToStringSlice(sliceOfString))   //
fmt.Println(cast.ToStringSlice(stringFields))      //
fmt.Println(cast.ToStringSlice(any))               //
}
</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>转为mapType类型</h2>
<p><code>cast</code>库能将传入的参数转为<code>mapType</code>类型,<code>Type</code>为上面支持的类型。</p>
<p>其实只需要分析一个<code>ToStringMapStringE</code>函数就可以了,其他的实现基本一样。<code>ToStringMapStringE</code>返回<code>mapstring</code>类型的值。</p>
<p>源码如下:</p>
<div class="jb51code"><pre class="brush:go;">func ToStringMapStringE(i interface{}) (mapstring, error) {
var m = mapstring{}

switch v := i.(type) {
case mapstring:
    return v, nil
case mapinterface{}:
    for k, val := range v {
      m = ToString(val)
    }
    return m, nil
case mapstring:
    for k, val := range v {
      m = ToString(val)
    }
    return m, nil
case mapinterface{}:
    for k, val := range v {
      m = ToString(val)
    }
    return m, nil
case string:
    err := jsonStringToObject(v, &amp;m)
    return m, err
default:
    return m, fmt.Errorf("unable to cast %#v of type %T to mapstring", i, i)
}
}
</pre></div>
<p>根据传入的参数类型:</p>
<ul><li>如果是<code>mapstring</code>,不用转换,直接返回;</li><li>如果是<code>mapinterface{}</code>,将每个值转为<code>string</code>存入新的 map,最后返回新的 map;</li><li>如果是<code>mapstring</code>,将每个键转为<code>string</code>存入新的 map,最后返回新的 map;</li><li>如果是<code>mapinterface{}</code>,将每个键和值都转为<code>string</code>存入新的 map,最后返回新的 map;</li><li>如果是<code>string</code>类型,<code>cast</code>将它看成一个 JSON 串,解析这个 JSON 到<code>mapstring</code>,然后返回结果;</li><li>其他情况,返回错误。</li></ul>
<p>示例:</p>
<div class="jb51code"><pre class="brush:go;">package main

import (
"fmt"

"github.com/spf13/cast"
)

func main() {
m1 := mapstring {
    "name": "darjun",
    "job": "developer",
}

m2 := mapinterface{} {
    "name": "jingwen",
    "age": 18,
}

m3 := mapstring {
    "name": "pipi",
    "job": "designer",
}

m4 := mapinterface{} {
    "name": "did",
    "age": 29,
}

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

fmt.Println(cast.ToStringMapString(m1))      // map
fmt.Println(cast.ToStringMapString(m2))      // map
fmt.Println(cast.ToStringMapString(m3))      // map
fmt.Println(cast.ToStringMapString(m4))      // map
fmt.Println(cast.ToStringMapString(jsonStr)) // map
}
</pre></div>
頁: [1]
查看完整版本: Go语言类型转换工具库cast的实现