cast для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Библиотека 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                    

Рекомендаций