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

  1. Reorder so `dive` comes right before `keys`: `dive,keys,<key-validators>,endkeys,<value-validators>`
  2. Remove the stray `keys` tag if map-key validation is not intended
  3. Fix the constant/aliased tag expression that emits keys without dive
  4. 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

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


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