Type Assertion and Type Switch
Type assertion extracts a concrete type from an interface value, while type switch branches on the type. Together they enable safe polymorphism in Go.
Type Assertionβ
Assert that an interface value holds a specific type and extract that value.
package main
import "fmt"
type Animal interface {
Sound() string
}
type Dog struct{ Name string }
type Cat struct{ Name string }
func (d Dog) Sound() string { return "woof" }
func (c Cat) Sound() string { return "meow" }
func main() {
var a Animal = Dog{Name: "Rex"}
// 1. Simple type assertion β panics on failure!
dog := a.(Dog)
fmt.Println(dog.Name, dog.Sound()) // Rex woof
// 2. Safe type assertion β ok pattern (recommended)
if dog, ok := a.(Dog); ok {
fmt.Println("Dog:", dog.Name)
} else {
fmt.Println("not a Dog")
}
if cat, ok := a.(Cat); ok {
fmt.Println("Cat:", cat.Name)
} else {
fmt.Println("not a Cat") // printed
}
// 3. Wrong type assertion β causes panic
defer func() {
if r := recover(); r != nil {
fmt.Println("panic recovered:", r)
}
}()
_ = a.(Cat) // panic: interface conversion: interface {} is Dog, not Cat
}
Safe Type Assertion Patternsβ
Always use the ok pattern in production code.
package main
import "fmt"
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
type Triangle struct{ Base, Height float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (t Triangle) Area() float64 { return 0.5 * t.Base * t.Height }
// Check for optional interface support
type Resizable interface {
Resize(factor float64) Shape
}
func (c Circle) Resize(factor float64) Shape {
return Circle{Radius: c.Radius * factor}
}
func processShape(s Shape) {
fmt.Printf("base area: %.2f\n", s.Area())
// Check if optional capability is supported
if r, ok := s.(Resizable); ok {
bigger := r.Resize(2.0)
fmt.Printf("2x area: %.2f\n", bigger.Area())
} else {
fmt.Println("this shape doesn't support resizing")
}
}
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 6},
Triangle{Base: 3, Height: 4},
}
for _, s := range shapes {
fmt.Printf("=== %T ===\n", s)
processShape(s)
}
}
Type Switchβ
Use switch v.(type) to branch on multiple types.
package main
import "fmt"
func describe(i any) string {
switch v := i.(type) {
case nil:
return "nil"
case int:
return fmt.Sprintf("integer: %d", v)
case float64:
return fmt.Sprintf("float: %.2f", v)
case bool:
if v {
return "boolean: true"
}
return "boolean: false"
case string:
return fmt.Sprintf("string: %q (len: %d)", v, len(v))
case []int:
return fmt.Sprintf("int slice: %v (len: %d)", v, len(v))
case map[string]any:
return fmt.Sprintf("map: %v", v)
case error:
return fmt.Sprintf("error: %s", v.Error())
default:
return fmt.Sprintf("unknown type: %T = %v", v, v)
}
}
func main() {
values := []any{
nil,
42,
3.14,
true,
false,
"hello",
[]int{1, 2, 3},
map[string]any{"key": "value"},
fmt.Errorf("something went wrong"),
struct{ X, Y int }{1, 2},
}
for _, v := range values {
fmt.Println(describe(v))
}
}
Type Switch with Interface Combinationsβ
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
type Triangle struct{ A, B, C float64 } // side lengths
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (t Triangle) Area() float64 {
// Heron's formula
s := (t.A + t.B + t.C) / 2
return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))
}
// Type-specific detail output
func shapeDetail(s Shape) string {
switch v := s.(type) {
case Circle:
return fmt.Sprintf("Circle(r=%.1f, d=%.1f)", v.Radius, v.Radius*2)
case Rectangle:
return fmt.Sprintf("Rectangle(%.1fΓ%.1f)", v.Width, v.Height)
case Triangle:
return fmt.Sprintf("Triangle(sides: %.1f, %.1f, %.1f)", v.A, v.B, v.C)
default:
return fmt.Sprintf("unknown shape (%T)", v)
}
}
// Handle multiple types in one case
func isPolygon(s Shape) bool {
switch s.(type) {
case Rectangle, Triangle:
return true
default:
return false
}
}
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 3, Height: 4},
Triangle{A: 3, B: 4, C: 5},
}
for _, s := range shapes {
fmt.Printf("%s\n area=%.2f, polygon=%v\n",
shapeDetail(s), s.Area(), isPolygon(s))
}
}
Type Assertion in JSON Parsingβ
A common real-world pattern: type assertion after JSON parsing.
package main
import (
"encoding/json"
"fmt"
)
func parseJSON(data string) {
var result any
if err := json.Unmarshal([]byte(data), &result); err != nil {
fmt.Println("parse error:", err)
return
}
// JSON parsing results in map[string]any, []any, float64, string, bool, nil
processValue("root", result)
}
func processValue(key string, v any) {
switch val := v.(type) {
case map[string]any:
fmt.Printf("%s: {object}\n", key)
for k, child := range val {
processValue(key+"."+k, child)
}
case []any:
fmt.Printf("%s: [array, len=%d]\n", key, len(val))
for i, item := range val {
processValue(fmt.Sprintf("%s[%d]", key, i), item)
}
case float64:
fmt.Printf("%s: number=%v\n", key, val)
case string:
fmt.Printf("%s: string=%q\n", key, val)
case bool:
fmt.Printf("%s: bool=%v\n", key, val)
case nil:
fmt.Printf("%s: null\n", key)
}
}
func main() {
jsonData := `{
"name": "John Doe",
"age": 30,
"active": true,
"scores": [95.5, 87.0, 92.3],
"address": {
"city": "Seoul",
"zip": "04524"
},
"memo": null
}`
parseJSON(jsonData)
}
errors.As and errors.Is β Error Type Assertionβ
The standard way to use type assertion with errors in Go.
package main
import (
"errors"
"fmt"
)
// Custom error types
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error [%s]: %s", e.Field, e.Message)
}
type NotFoundError struct {
Resource string
ID int
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s(ID=%d) not found", e.Resource, e.ID)
}
var ErrPermissionDenied = errors.New("permission denied")
func findUser(id int) error {
if id <= 0 {
return &ValidationError{Field: "id", Message: "must be positive"}
}
if id > 100 {
return fmt.Errorf("user lookup failed: %w", &NotFoundError{Resource: "User", ID: id})
}
if id == 13 {
return fmt.Errorf("access blocked: %w", ErrPermissionDenied)
}
return nil
}
func handleError(err error) {
if err == nil {
fmt.Println("success!")
return
}
// errors.As: unwrap to specific type
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Printf("input error β field: %s, message: %s\n", ve.Field, ve.Message)
return
}
var nfe *NotFoundError
if errors.As(err, &nfe) {
fmt.Printf("not found β %s ID=%d\n", nfe.Resource, nfe.ID)
return
}
// errors.Is: check for specific error value
if errors.Is(err, ErrPermissionDenied) {
fmt.Println("permission error β login required")
return
}
fmt.Println("unknown error:", err)
}
func main() {
testCases := []int{-1, 42, 999, 13}
for _, id := range testCases {
fmt.Printf("ID=%d: ", id)
handleError(findUser(id))
}
}
Upcasting and Downcasting Interfacesβ
package main
import "fmt"
type Base interface {
BaseMethod() string
}
type Extended interface {
Base
ExtendedMethod() string
}
type MyType struct {
value string
}
func (m MyType) BaseMethod() string { return "base: " + m.value }
func (m MyType) ExtendedMethod() string { return "extended: " + m.value }
func useBase(b Base) {
fmt.Println(b.BaseMethod())
// Downcast: Base β Extended
if ext, ok := b.(Extended); ok {
fmt.Println(ext.ExtendedMethod())
}
// Cast to concrete type
if mt, ok := b.(MyType); ok {
fmt.Println("concrete type:", mt.value)
}
}
func main() {
m := MyType{value: "hello"}
// Upcast: concrete type β interface (automatic)
var b Base = m
var e Extended = m
useBase(b)
fmt.Println("---")
useBase(e) // Extended also implements Base
}
Key Summary
v.(T): panics on failure β only use when certainv, ok := i.(T): ok pattern β always use this in productionswitch v := i.(type): branch on multiple types β ideal for type-based dispatcherrors.As/errors.Is: standard way to do type assertion with errors- Type assertion only works on interface values (not concrete types)