Библиотека cast для Go: Полное руководство по конвертации типов
В языке Go строгая статическая типизация — это одновременно и сила, и вызов. С одной стороны, компилятор ловит множество ошибок на этапе сборки, с другой — разработчику приходится писать многословный код для преобразования типов, особенно при работе с данными из внешних источников (JSON, YAML, базы данных, HTTP-запросы). Библиотека cast от Spf13 (автора Cobra и Viper) решает эту проблему элегантно и лаконично.
cast предоставляет набор функций для безопасного и удобного приведения интерфейсов (interface{}) к конкретным типам Go. Вместо того чтобы писать громоздкие конструкции с type assertion и проверками ошибок, вы вызываете одну функцию, которая возвращает значение по умолчанию или ошибку, если преобразование невозможно. Это особенно полезно при работе с конфигурациями, парсингом ответов API и любыми сценариями, где тип данных известен только во время выполнения.
Библиотека не использует рефлексию в каждом вызове (хотя некоторые функции полагаются на неё для сложных преобразований), и спроектирована так, чтобы быть максимально производительной для типичных случаев. Она является частью экосистемы Viper, но может использоваться как самостоятельная утилита. Если вы пишете на Go и сталкиваетесь с необходимостью конвертации строк в числа, интерфейсов в слайсы или мап в структуры — cast станет вашим незаменимым помощником.
Установка
Установка библиотеки производится стандартным инструментом go get. Выполните следующую команду в терминале внутри вашего Go-модуля:
go get github.com/spf13/cast
После выполнения команды библиотека будет добавлена в файл go.mod и готова к использованию. Убедитесь, что у вас установлен Go версии 1.16 или выше (рекомендуется 1.18+ для поддержки generics, хотя cast их не использует).
Быстрый старт
Давайте рассмотрим минимальный рабочий пример, который демонстрирует основные возможности библиотеки: преобразование строки в целое число и интерфейса в строку.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
// Преобразуем строку в int
num, err := cast.ToIntE("42")
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Printf("Число: %d (тип: %T)\n", num, num)
// Преобразуем interface{} в строку
var val interface{} = "hello world"
str := cast.ToString(val)
fmt.Printf("Строка: %s (тип: %T)\n", str, str)
// Преобразуем с fallback (без ошибки)
safeNum := cast.ToInt("не число") // вернет 0
fmt.Printf("Безопасное число: %d\n", safeNum)
}
Как видите, код чистый и понятный. Функции с суффиксом E возвращают ошибку, без суффикса — возвращают нулевое значение при неудаче.
Основные методы и функции
Библиотека cast предлагает широкий спектр функций для работы с различными типами данных. Ниже приведены 8+ ключевых функций с описанием и примерами.
1. ToString
Сигнатура: func ToString(i interface{}) string
Что делает: Преобразует переданное значение в строку. Поддерживает числовые типы, булевы значения, слайсы байт, строки и интерфейсы.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
fmt.Println(cast.ToString(123)) // "123"
fmt.Println(cast.ToString(45.67)) // "45.67"
fmt.Println(cast.ToString(true)) // "true"
fmt.Println(cast.ToString([]byte("go"))) // "go"
fmt.Println(cast.ToString(nil)) // ""
}
2. ToInt
Сигнатура: func ToInt(i interface{}) int
Что делает: Преобразует значение в int. Работает со строками, числами с плавающей точкой (отбрасывает дробную часть), другими целочисленными типами.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
fmt.Println(cast.ToInt("100")) // 100
fmt.Println(cast.ToInt(99.9)) // 99 (дробная часть отброшена)
fmt.Println(cast.ToInt(int8(50))) // 50
fmt.Println(cast.ToInt("abc")) // 0 (ошибка, но возвращен 0)
}
3. ToInt64
Сигнатура: func ToInt64(i interface{}) int64
Что делает: Аналог ToInt, но возвращает int64. Полезно для работы с временными метками Unix и большими числами.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
var bigNum interface{} = "9223372036854775807"
result := cast.ToInt64(bigNum)
fmt.Printf("Результат: %d (тип: %T)\n", result, result)
// Работа с временем
timestamp := cast.ToInt64(1633027200)
fmt.Println("Unix timestamp:", timestamp)
}
4. ToFloat64
Сигнатура: func ToFloat64(i interface{}) float64
Что делает: Преобразует значение в float64. Поддерживает строки, целые числа и другие типы с плавающей точкой.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
fmt.Println(cast.ToFloat64("3.14")) // 3.14
fmt.Println(cast.ToFloat64(42)) // 42.0
fmt.Println(cast.ToFloat64("не число")) // 0.0
}
5. ToBool
Сигнатура: func ToBool(i interface{}) bool
Что делает: Преобразует значение в bool. Распознает строки "true", "false", "1", "0", "yes", "no", "on", "off" (регистронезависимо), а также целые числа (0 — false, не 0 — true).
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
fmt.Println(cast.ToBool("true")) // true
fmt.Println(cast.ToBool("FALSE")) // false
fmt.Println(cast.ToBool(1)) // true
fmt.Println(cast.ToBool("yes")) // true
fmt.Println(cast.ToBool("no")) // false
fmt.Println(cast.ToBool("invalid")) // false
}
6. ToStringSlice
Сигнатура: func ToStringSlice(i interface{}) []string
Что делает: Преобразует интерфейс в слайс строк. Поддерживает слайсы любых типов, где каждый элемент конвертируется в строку, а также строки (разбивает по пробелам).
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
// Из слайса интерфейсов
items := []interface{}{"apple", 42, true}
strSlice := cast.ToStringSlice(items)
fmt.Println(strSlice) // ["apple", "42", "true"]
// Из строки
words := cast.ToStringSlice("hello world go")
fmt.Println(words) // ["hello", "world", "go"]
}
7. ToIntSlice
Сигнатура: func ToIntSlice(i interface{}) []int
Что делает: Преобразует интерфейс в слайс целых чисел. Каждый элемент конвертируется через ToInt.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
data := []interface{}{"10", 20, 30.5}
ints := cast.ToIntSlice(data)
fmt.Println(ints) // [10, 20, 30]
// Если элемент не конвертируется, он заменяется на 0
mixed := []interface{}{"abc", 5}
fmt.Println(cast.ToIntSlice(mixed)) // [0, 5]
}
8. ToStringMapString
Сигнатура: func ToStringMapString(i interface{}) map[string]string
Что делает: Преобразует map[string]interface{} или подобные структуры в map[string]string. Все значения конвертируются в строки.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
rawMap := map[string]interface{}{
"name": "Alice",
"age": 30,
"active": true,
}
strMap := cast.ToStringMapString(rawMap)
fmt.Println(strMap) // map[active:true age:30 name:Alice]
}
9. ToDuration
Сигнатура: func ToDuration(i interface{}) time.Duration
Что делает: Преобразует строку (например, "5s", "10m", "2h") или числовое значение (в наносекундах) в time.Duration.
package main
import (
"fmt"
"time"
"github.com/spf13/cast"
)
func main() {
dur1 := cast.ToDuration("30s")
fmt.Println(dur1) // 30s
dur2 := cast.ToDuration(5000000000) // 5 секунд в наносекундах
fmt.Println(dur2) // 5s
// Если строка невалидна, возвращается 0
fmt.Println(cast.ToDuration("invalid")) // 0s
}
10. ToTime
Сигнатура: func ToTime(i interface{}) time.Time
Что делает: Преобразует строку (в различных форматах) или целое число (Unix timestamp) в time.Time.
package main
import (
"fmt"
"github.com/spf13/cast"
)
func main() {
// Из строки в формате RFC3339
t1 := cast.ToTime("2023-10-01T12:00:00Z")
fmt.Println(t1) // 2023-10-01 12:00:00 +0000 UTC
// Из Unix timestamp
t2 := cast.ToTime(int64(1696156800))
fmt.Println(t2) // 2023-10-01 12:00:00 +0000 UTC
// Из невалидной строки — нулевое время
fmt.Println(cast.ToTime("not a time")) // 0001-01-01 00:00:00 +0000 UTC
}
Реальный практический пример
Рассмотрим реальный сценарий: мы пишем микросервис, который получает данные о пользователе из внешнего API (в формате JSON) и сохраняет их в базу данных. Данные приходят в виде map[string]interface{}, и нам нужно безопасно извлечь значения разных типов.
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/spf13/cast"
)
// User представляет модель пользователя в нашей системе.
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
Salary float64 `json:"salary"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
Tags []string `json:"tags"`
}
// parseUserData преобразует "сырые" данные из API в структуру User.
func parseUserData(rawData map[string]interface{}) (User, error) {
user := User{}
// ID — int64
user.ID = cast.ToInt64(rawData["id"])
// Name — строка
user.Name = cast.ToString(rawData["name"])
// Email — строка с проверкой
email := cast.ToString(rawData["email"])
if email == "" {
return user, fmt.Errorf("email is required")
}
user.Email = email
// Age — int
user.Age = cast.ToInt(rawData["age"])
// Salary — float64
user.Salary = cast.ToFloat64(rawData["salary"])
// IsActive — bool
user.IsActive = cast.ToBool(rawData["is_active"])
// CreatedAt — time.Time
user.CreatedAt = cast.ToTime(rawData["created_at"])
// Tags — []string
tags := cast.ToStringSlice(rawData["tags"])
if tags == nil {
tags = []string{}
}
user.Tags = tags
return user, nil
}
func main() {
// Имитация ответа от внешнего API
jsonData := `{
"id": 1234567890,
"name": "John Doe",
"email": "john@example.com",
"age": "30",
"salary": "75000.50",
"is_active": "yes",
"created_at": "2024-01-15T10:30:00Z",
"tags": ["developer", "go", "backend"]
}`
var rawMap map[string]interface{}
if err := json.Unmarshal([]byte(jsonData), &rawMap); err != nil {
log.Fatalf("Failed to parse JSON: %v", err)
}
user, err := parseUserData(rawMap)
if err != nil {
log.Fatalf("Failed to parse user data: %v", err)
}
fmt.Printf("Parsed User: %+v\n", user)
fmt.Printf("Name: %s, Age: %d, Salary: %.2f, Active: %t\n",
user.Name, user.Age, user.Salary, user.IsActive)
fmt.Printf("Tags: %v\n", user.Tags)
// Пример обработки ошибок с функциями E
invalidData := map[string