← Debugging Guides

阅读中文版本

Go interface conversion panic — Wrong Dynamic Type

Fix "interface conversion: interface {} is X, not Y" by using the two-value type assertion or a type switch instead of a single-value cast.

intermediatedebugginggotype-assertionpanic

Symptoms

  • panic: interface conversion: interface {} is string, not int
  • The panic names two concrete types you did not expect
  • The failure appears only for certain JSON or map inputs

Possible causes

  • A single-value assertion v := x.(T) is used where x may hold another dynamic type
  • JSON numbers decode as float64, so asserting to int panics
  • A map value stored as one type is read assuming another

Diagnosis steps

Reproduce and read the panic types

go run - <<'EOF'
package main
import "fmt"
func main(){ var v any = "7"; n := v.(int); fmt.Println(n) }
EOF

Interpretation

The panic message gives the actual and asserted types, so you can correct the assertion or input.

Switch to the comma-ok form

n, ok := v.(int); if !ok { /* handle */ }

Interpretation

The two-value assertion never panics and lets you branch on the missing type instead of crashing.

Root cause

A single-value type assertion x.(T) panics when the dynamic type of x is not exactly T. JSON and interface-heavy code frequently store unexpected concrete types, so the unsafe form is a latent runtime crash.

Fix

Use the comma-ok assertion to check before use, or a type switch to handle each concrete type (remembering JSON numbers are float64), and validate at the boundary where untyped data enters.

Risk note

Converting JSON numbers straight to int truncates; decode into a typed struct with json.Decoder.UseNumber when precision matters.

Verify the fix

Rerun with the mismatched input and confirm the guard branch is taken instead of a panic. Add table tests for string, int, and float64 inputs.

Symptoms

When you hit “Go interface conversion panic — Wrong Dynamic Type”, guessing wastes time. The guide below follows evidence: reproduce the symptom, run targeted diagnostics, identify the root cause, apply the fix, and verify it with concrete checks.

Observed symptoms include:

  • panic: interface conversion: interface {} is string, not int
  • The panic names two concrete types you did not expect
  • The failure appears only for certain JSON or map inputs

Reproduce and Diagnose

Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.

Step 1: Reproduce and read the panic types

go run - <<'EOF'
package main
import "fmt"
func main(){ var v any = "7"; n := v.(int); fmt.Println(n) }
EOF

The panic message gives the actual and asserted types, so you can correct the assertion or input.

Step 2: Switch to the comma-ok form

n, ok := v.(int); if !ok { /* handle */ }

The two-value assertion never panics and lets you branch on the missing type instead of crashing.

Likely contributors, in order of frequency:

  • A single-value assertion v := x.(T) is used where x may hold another dynamic type
  • JSON numbers decode as float64, so asserting to int panics
  • A map value stored as one type is read assuming another

Root Cause

A single-value type assertion x.(T) panics when the dynamic type of x is not exactly T. JSON and interface-heavy code frequently store unexpected concrete types, so the unsafe form is a latent runtime crash.

The Fix

Use the comma-ok assertion to check before use, or a type switch to handle each concrete type (remembering JSON numbers are float64), and validate at the boundary where untyped data enters.

Watch out: Converting JSON numbers straight to int truncates; decode into a typed struct with json.Decoder.UseNumber when precision matters.

func asInt(v any) (int, bool) {
	switch n := v.(type) {
	case int:
		return n, true
	case float64: // JSON numbers decode here
		return int(n), true
	case json.Number:
		i, err := n.Int64()
		return int(i), err == nil
	default:
		return 0, false
	}
}

if n, ok := asInt(raw); ok {
	fmt.Println(n)
}

Verify and Prevent

Rerun with the mismatched input and confirm the guard branch is taken instead of a panic. Add table tests for string, int, and float64 inputs.

Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.

This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.