go-playground/validator · critical
dive error! can't dive on a non slice or map
Error message
dive error! can't dive on a non slice or map
What it means
validator.go:365 panics when the `dive` tag is applied to a field that is neither a slice, array, nor map (and not otherwise iterable). `dive` tells the executor to apply the remaining tags to each element; the compiled plan reaches the dive case in traverseField, finds no iterable kind in the default branch, and panics. Validation plans are cached, so the panic happens on first validation of the offending type.
Source
Thrown at validator.go:365
switch val.Kind() {
case reflect.Ptr:
if val.Elem().Kind() == reflect.Struct {
// Dive into the struct so its own tags run
v.traverseField(ctx, parent, val, ns, structNs, reusableCF, nil)
}
case reflect.Struct:
v.traverseField(ctx, parent, val, ns, structNs, reusableCF, nil)
}
}
} else {
v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
}
}
default:
// throw error, if not a slice or map then should not have gotten here
// bad dive tag
panic("dive error! can't dive on a non slice or map")
}
return
case typeOr:
v.misc = v.misc[0:0]
for {
// set Field Level fields
v.slflParent = parent
v.flField = current
v.cf = cf
v.ct = ct
if ct.fn(ctx, v) {
if ct.isBlockEnd {
ct = ct.nextView on GitHub (pinned to facf128d2e)
Solutions
- Remove the `dive` tag from fields that are not slices, arrays, or maps.
- If the field type changed, update the tag chain to match the new type (plain tags, or dive only on the slice wrapper).
- To validate elements of a slice of structs, use `dive` plus `required` on struct elements — but ensure the field itself is []T or map[K]T.
- For non-iterable custom container types, register a custom validator or struct-level validation instead of dive.
- Locate the offending field via the validation namespace reported before the panic / the type being validated at first Struct() call.
Example fix
// before
type Config struct {
Name string `validate:"dive,required"` // string, not a slice
}
// after
type Config struct {
Name string `validate:"required"`
Tags []string `validate:"dive,required"` // dive only here
} Defensive patterns
Strategy: validation
Validate before calling
func ensureDiveIsValid(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Kind() == reflect.Slice || t.Kind() == reflect.Array || t.Kind() == reflect.Map
} Try / catch
func safeValidate(v *validator.Validate, s interface{}) (err error) {
defer func() {
if r := recover(); r != nil {
if strings.Contains(fmt.Sprint(r), "dive error") {
err = errors.New("dive tag applied to non slice/map field")
return
}
panic(r)
}
}()
return v.Struct(s)
} Prevention
- Only put dive on slice, array, or map fields; audit tags when changing field types.
- Search the codebase for `dive,` and check each field's kind.
- Cover each annotated struct with a validation unit test that runs on every build.
- Remember dive does not traverse into plain structs; use struct-level validation instead.
When it happens
Trigger: A struct field of type string/struct/int with `validate:"dive,..."` (dive only makes sense on slices, arrays, maps); adding `dive` after changing a field from []string to string; using dive on a pointer or interface that is not a slice/map; nested expressions where dive ends up before a non-iterable tag chain.
Common situations: Refactoring a slice field to a scalar (or vice versa) without updating tags; copy-pasting `dive,required` onto every field; misunderstanding that dive also works on nested structs (it does not — use struct-level recursion); applying dive to time.Time or custom wrapper types.
Related errors
- Bad field type %T
- panic(err) (os.Stat *PathError re-raised for unexpected stat
- Bad param number for required_if %s
- Duplicate param %s for required_if %s
- Bad param number for excluded_if %s
AI-assisted analysis of go-playground/validator@facf128d2e (2026-09-02).
Data as JSON: /api/errors/a824840910607834.
Report an issue: GitHub.