Go Daily Vault актерский состав

Go

Введение

Сегодня мы познакомим вас с еще одной библиотекой spf13 God.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]TypeTypeдля вышеупомянутых типов):

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

Расширенное преобразование

castПредусмотрено два набора функций:

  • ToTypeTypeможет быть любого поддерживаемого типа), преобразуйте параметр в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, возврат напрямую;
  • Если целое число, передать аргумент как отметку времени (поскольку время UTC1970.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[interface{}]string, превращая каждый ключ вstringСохраните новую карту и, наконец, верните новую карту;
  • еслиmap[interface{}]interface{}, преобразует каждый ключ и значение вstringСохраните новую карту и, наконец, верните новую карту;
  • если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

я

мой блог

Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись WeChat [GoUpUp], учитесь вместе и добивайтесь прогресса вместе ~

Эта статья опубликована в блогеOpenWriteвыпускать!