
How to Convert Data Types in Go – Practical Guide
Data type conversion in Go is a fundamental skill that every developer needs to master, especially when building robust server applications or microservices. Unlike languages that perform automatic type conversions, Go requires explicit conversion between types, which gives you more control but can be tricky for newcomers. This guide covers everything from basic conversions between primitives to handling complex scenarios with interfaces, JSON, and custom types, plus the common pitfalls that can crash your production servers.
Understanding Go’s Type System
Go is statically typed with strict type checking, meaning you can’t just throw an int where a string belongs. The type system prevents many runtime errors but requires you to be explicit about conversions. Go distinguishes between type conversion (changing the representation) and type assertion (extracting a value from an interface).
Here’s the basic syntax for type conversion:
T(value) // Convert value to type T
This works for compatible types, but you’ll need different approaches for complex conversions.
Converting Between Numeric Types
Numeric conversions are straightforward but can lose precision or overflow. Here are the common patterns:
package main
import "fmt"
func main() {
// Integer conversions
var i32 int32 = 42
var i64 int64 = int64(i32) // Safe conversion
var i16 int16 = int16(i32) // Potential data loss
// Float conversions
var f32 float32 = 3.14159
var f64 float64 = float64(f32)
// Float to int (truncates decimal)
var intFromFloat int = int(f64)
fmt.Printf("int32: %d, int64: %d, int16: %d\n", i32, i64, i16)
fmt.Printf("float32: %f, float64: %f, int: %d\n", f32, f64, intFromFloat)
}
String Conversions with strconv Package
The strconv
package is your go-to tool for converting between strings and other types. Unlike simple type conversion, these functions handle parsing and can return errors.
package main
import (
"fmt"
"strconv"
)
func main() {
// String to int
str := "123"
num, err := strconv.Atoi(str)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Converted: %d\n", num)
}
// Int to string
intVal := 456
strVal := strconv.Itoa(intVal)
fmt.Printf("String: %s\n", strVal)
// More specific conversions
floatStr := "3.14159"
floatVal, err := strconv.ParseFloat(floatStr, 64)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Float: %f\n", floatVal)
}
// Bool conversions
boolStr := "true"
boolVal, err := strconv.ParseBool(boolStr)
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf("Bool: %t\n", boolVal)
}
}
Working with Interfaces and Type Assertions
When dealing with empty interfaces (interface{}
) or custom interfaces, you need type assertions to extract the underlying values. This is common in JSON unmarshaling and generic code.
package main
import "fmt"
func main() {
var i interface{} = "hello world"
// Type assertion with ok pattern (safe)
str, ok := i.(string)
if ok {
fmt.Printf("String value: %s\n", str)
} else {
fmt.Println("Not a string")
}
// Type assertion without ok (can panic)
// str2 := i.(string) // Use carefully
// Type switch for multiple types
var values []interface{} = []interface{}{42, "hello", 3.14, true}
for _, v := range values {
switch val := v.(type) {
case int:
fmt.Printf("Integer: %d\n", val)
case string:
fmt.Printf("String: %s\n", val)
case float64:
fmt.Printf("Float: %f\n", val)
case bool:
fmt.Printf("Boolean: %t\n", val)
default:
fmt.Printf("Unknown type: %T\n", val)
}
}
}
JSON and Struct Conversions
Converting between JSON and Go structures is extremely common in web applications and APIs. The encoding/json
package handles most cases, but custom types need special attention.
package main
import (
"encoding/json"
"fmt"
"time"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
JoinDate time.Time `json:"join_date"`
}
func main() {
// Struct to JSON
user := User{
ID: 1,
Name: "John Doe",
Email: "john@example.com",
JoinDate: time.Now(),
}
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Printf("Marshal error: %v\n", err)
return
}
fmt.Printf("JSON: %s\n", jsonData)
// JSON to struct
jsonStr := `{"id": 2, "name": "Jane Smith", "email": "jane@example.com", "join_date": "2023-01-15T10:30:00Z"}`
var newUser User
err = json.Unmarshal([]byte(jsonStr), &newUser)
if err != nil {
fmt.Printf("Unmarshal error: %v\n", err)
return
}
fmt.Printf("Unmarshaled: %+v\n", newUser)
// Working with unknown JSON structure
var generic interface{}
err = json.Unmarshal([]byte(jsonStr), &generic)
if err != nil {
fmt.Printf("Generic unmarshal error: %v\n", err)
return
}
// Type assertion to access values
if userMap, ok := generic.(map[string]interface{}); ok {
if name, ok := userMap["name"].(string); ok {
fmt.Printf("Name from generic: %s\n", name)
}
}
}
Custom Type Conversions
For custom types, you can implement methods to handle conversions. This is particularly useful for domain-specific types or when integrating with external systems.
package main
import (
"fmt"
"strconv"
"strings"
)
// Custom type for user roles
type UserRole int
const (
Admin UserRole = iota
Moderator
User
)
// String method for string conversion
func (ur UserRole) String() string {
switch ur {
case Admin:
return "admin"
case Moderator:
return "moderator"
case User:
return "user"
default:
return "unknown"
}
}
// ParseUserRole converts string to UserRole
func ParseUserRole(s string) (UserRole, error) {
switch strings.ToLower(s) {
case "admin":
return Admin, nil
case "moderator":
return Moderator, nil
case "user":
return User, nil
default:
return 0, fmt.Errorf("invalid user role: %s", s)
}
}
// Custom type for ID that can be string or int
type FlexibleID struct {
Value interface{}
}
func (f FlexibleID) String() string {
switch v := f.Value.(type) {
case string:
return v
case int:
return strconv.Itoa(v)
case int64:
return strconv.FormatInt(v, 10)
default:
return fmt.Sprintf("%v", v)
}
}
func main() {
role := Admin
fmt.Printf("Role as string: %s\n", role.String())
roleStr := "moderator"
parsedRole, err := ParseUserRole(roleStr)
if err != nil {
fmt.Printf("Parse error: %v\n", err)
} else {
fmt.Printf("Parsed role: %s\n", parsedRole)
}
// Flexible ID examples
id1 := FlexibleID{Value: 12345}
id2 := FlexibleID{Value: "user-abc-123"}
fmt.Printf("ID1: %s, ID2: %s\n", id1.String(), id2.String())
}
Performance Comparison of Conversion Methods
Different conversion methods have varying performance characteristics. Here’s a comparison based on common scenarios:
Conversion Type | Method | Performance | Safety | Use Case |
---|---|---|---|---|
Numeric | Direct casting | Fastest (~1ns) | Can overflow | Known safe ranges |
String to Int | strconv.Atoi | ~50ns | Returns error | User input validation |
String to Int | strconv.ParseInt | ~45ns | Returns error | Specific bit sizes |
Type Assertion | value.(Type) | ~2ns | Can panic | Known interface types |
Type Assertion | value.(Type) with ok | ~3ns | Safe | Unknown interface types |
JSON | json.Marshal/Unmarshal | ~1000ns+ | Returns error | API communication |
Common Pitfalls and Best Practices
Here are the most common issues developers encounter with type conversions and how to avoid them:
- Integer Overflow: Always check ranges when converting between different sized integers
- Panic on Type Assertions: Use the comma ok idiom unless you’re absolutely certain of the type
- Precision Loss: Converting from float64 to float32 or float to int loses precision
- JSON Number Handling: JSON unmarshaling defaults to float64 for numbers, not int
- String Performance: Avoid repeated string conversions in loops; cache the result
// BAD: Can panic
func badConversion(i interface{}) {
str := i.(string) // Will panic if i is not a string
fmt.Println(str)
}
// GOOD: Safe conversion
func goodConversion(i interface{}) {
if str, ok := i.(string); ok {
fmt.Println(str)
} else {
fmt.Println("Not a string")
}
}
// BAD: Overflow without checking
func badNumericConversion(big int64) int32 {
return int32(big) // Can overflow
}
// GOOD: Check ranges
func goodNumericConversion(big int64) (int32, error) {
if big > math.MaxInt32 || big < math.MinInt32 {
return 0, fmt.Errorf("value %d overflows int32", big)
}
return int32(big), nil
}
Real-World Use Cases
Here are practical scenarios where type conversion is essential:
HTTP Request Processing
package main
import (
"fmt"
"net/http"
"strconv"
)
func handleUserRequest(w http.ResponseWriter, r *http.Request) {
// Converting query parameters
userIDStr := r.URL.Query().Get("user_id")
if userIDStr == "" {
http.Error(w, "Missing user_id", http.StatusBadRequest)
return
}
userID, err := strconv.Atoi(userIDStr)
if err != nil {
http.Error(w, "Invalid user_id format", http.StatusBadRequest)
return
}
// Converting form values
ageStr := r.FormValue("age")
age, err := strconv.Atoi(ageStr)
if err != nil {
age = 0 // Default value
}
fmt.Fprintf(w, "User ID: %d, Age: %d", userID, age)
}
Database Integration
package main
import (
"database/sql"
"fmt"
"time"
)
type Product struct {
ID int64
Name string
Price float64
InStock bool
CreatedAt time.Time
}
func scanProduct(rows *sql.Rows) (*Product, error) {
var p Product
var createdAtStr string
err := rows.Scan(&p.ID, &p.Name, &p.Price, &p.InStock, &createdAtStr)
if err != nil {
return nil, err
}
// Convert string timestamp to time.Time
p.CreatedAt, err = time.Parse("2006-01-02 15:04:05", createdAtStr)
if err != nil {
return nil, fmt.Errorf("failed to parse created_at: %v", err)
}
return &p, nil
}
Advanced Conversion Techniques
For complex applications, you might need more sophisticated conversion strategies:
package main
import (
"fmt"
"reflect"
"strconv"
)
// Generic converter using reflection
func ConvertToString(value interface{}) string {
if value == nil {
return ""
}
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
return v.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(v.Uint(), 10)
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(v.Float(), 'f', -1, 64)
case reflect.Bool:
return strconv.FormatBool(v.Bool())
default:
return fmt.Sprintf("%v", value)
}
}
// Slice conversion utility
func ConvertSlice[T any, U any](slice []T, converter func(T) U) []U {
result := make([]U, len(slice))
for i, item := range slice {
result[i] = converter(item)
}
return result
}
func main() {
// Generic string conversion
fmt.Println(ConvertToString(42)) // "42"
fmt.Println(ConvertToString(3.14)) // "3.14"
fmt.Println(ConvertToString(true)) // "true"
// Slice conversion example
numbers := []int{1, 2, 3, 4, 5}
strings := ConvertSlice(numbers, func(n int) string {
return strconv.Itoa(n)
})
fmt.Printf("Converted slice: %v\n", strings)
}
Type conversion in Go requires understanding the language's strict type system and choosing the right approach for each scenario. The key is knowing when to use direct casting, strconv functions, type assertions, or custom conversion methods. Always handle errors gracefully and consider performance implications in hot paths. For more detailed information about Go's type system, check out the official Go specification and the strconv package documentation.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.