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.next

View on GitHub (pinned to facf128d2e)

Solutions

  1. Remove the `dive` tag from fields that are not slices, arrays, or maps.
  2. If the field type changed, update the tag chain to match the new type (plain tags, or dive only on the slice wrapper).
  3. 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.
  4. For non-iterable custom container types, register a custom validator or struct-level validation instead of dive.
  5. 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

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


AI-assisted analysis of go-playground/validator@facf128d2e (2026-09-02). Data as JSON: /api/errors/a824840910607834. Report an issue: GitHub.