go-playground/validator · critical
'%s' tag must be immediately preceded by the '%s' tag
Error message
'%s' tag must be immediately preceded by the '%s' tag
What it means
During struct-tag parsing (cached per type), the `keys` tag is only legal immediately after a `dive` tag. If `keys` appears at the start of the tag list or not directly after `dive`, cache.go panics: "'keys' tag must be immediately preceded by the 'dive' tag". This panic happens once when the struct type is first validated (tag cache build), not per value.
Source
Thrown at cache.go:216
if i == 0 {
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault}
firstCtag = current
} else {
prevTag = current.typeof
current.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
current = current.next
}
switch t {
case diveTag:
current.typeof = typeDive
case keysTag:
current.typeof = typeKeys
if i == 0 || prevTag != typeDive {
panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag))
}
// need to pass along only keys tag
// need to increment i to skip over the keys tags
b := make([]byte, 0, 64)
i++
for ; i < len(tags); i++ {
b = append(b, tags[i]...)
b = append(b, ',')
if tags[i] == endKeysTag {
break
}
}
current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false)View on GitHub (pinned to facf128d2e)
Solutions
- Reorder so `dive` comes right before `keys`: `dive,keys,<key-validators>,endkeys,<value-validators>`
- Remove the stray `keys` tag if map-key validation is not intended
- Fix the constant/aliased tag expression that emits keys without dive
- Add a smoke test that validates a sample struct at startup to catch tag errors early
Example fix
// before M map[string]string `validate:"keys,endkeys,required"` // after M map[string]string `validate:"dive,keys,required,endkeys,required"`
Defensive patterns
Strategy: validation
Validate before calling
// tag order check before Validate.Struct
func checkKeysOrder(t reflect.StructTag) error {
tags := strings.Split(t.Get("validate"), ",")
for i, tg := range tags {
if tg == "keys" && (i == 0 || tags[i-1] != "dive") {
return fmt.Errorf("keys must immediately follow dive")
}
}
return nil
} Try / catch
func buildAndValidate(s any) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("tag syntax panic: %v", r)
}
}()
return validate.Struct(s)
} Prevention
- Always write map-key validation as dive,keys,...,endkeys,...
- Run a validation smoke test on all structs at startup
- Do not edit dive/keys/endkeys sequences independently
- Document tag order in team conventions
When it happens
Trigger: Any Validate.Struct call on a struct whose field tag contains `keys` without a preceding `dive`, e.g. `validate:"keys,endkeys,uuid"` or `validate:"required,keys"`.
Common situations: Hand-writing map-key validation tags out of order; refactoring tags and dropping the dive; misunderstanding that keys must start a dive-into-map block.
Related errors
- 'endkeys' tag encountered without a corresponding 'keys' tag
- Invalid validation tag on field '%s'
- Bad field type %T
- panic(err) (os.Stat *PathError re-raised for unexpected stat
- Bad param number for required_if %s
AI-assisted analysis of go-playground/validator@facf128d2e (2026-09-02).
Data as JSON: /api/errors/d05e8f48184ec8a4.
Report an issue: GitHub.