Skip to content

fix: prevent panic on pointer fields in comparison validators - #1596

Open
1solomonwakhungu wants to merge 1 commit into
go-playground:masterfrom
1solomonwakhungu:fix/issue-907-panic-nil-pointer-required-if-omitempty
Open

fix: prevent panic on pointer fields in comparison validators#1596
1solomonwakhungu wants to merge 1 commit into
go-playground:masterfrom
1solomonwakhungu:fix/issue-907-panic-nil-pointer-required-if-omitempty

Conversation

@1solomonwakhungu

Copy link
Copy Markdown

Summary

Comparison validators (isGte, isGt, isLte, isLt, hasLengthOf) panic with "Bad field type *T" when called on nil pointer fields. This occurs when a pointer field (e.g. *int64) uses required_if alongside a threshold tag like gte=2, and the required_if condition evaluates to false — causing the threshold validator to run on the nil pointer instead of being skipped.

Reproduction

type SampleWithPointer struct {
    Type     string `json:"type" validate:"required"`
    Quantity *int64 `json:"quantity,omitempty" validate:"required_if=Type special,gte=2"`
}

validate := validator.New()
// Type is "notspecial", so required_if is false
// But gte still runs on the nil *int64 and panics
validate.Struct(&SampleWithPointer{Type: "notspecial"})
// panic: Bad field type *int64

Root Cause

When required_if evaluates to false for a pointer field, the validator chain continues to the next tag (gte). The comparison validators in baked_in.go do not handle reflect.Pointer, so they fall through to the panic(fmt.Sprintf("Bad field type %s", field.Type())) at the end of each function.

Fix

Two complementary changes:

  1. Dereference pointer fields early in comparison validators (isGte, isGt, isLte, isLt, hasLengthOf): At the top of each function, if field.Kind() == reflect.Pointer, nil pointers return false (no threshold is satisfied by the absence of a value), and non-nil pointers are dereferenced via field.Elem() so the existing type-specific comparison logic handles the underlying value naturally.

  2. Skip non-nil-checkable validators on nil pointers in traverseField: When traverseField encounters a nil reflect.Ptr or reflect.Interface and the current tag is not marked runValidationWhenNil, it now returns early instead of continuing to execute validators that will panic.

Design Decisions

  • Nil pointer returns false: A nil pointer represents the absence of a value. No threshold (gte, gt, lte, lt, len) is satisfied by absence, so returning false is semantically correct. This is consistent with how omitempty behaves — if the field is nil, validation is skipped (no error), and when the field is present, the threshold applies normally.
  • Early dereference pattern: Dereferencing at the top of each function (before the switch) is cleaner than adding a reflect.Pointer case inside the switch, because it lets the existing type cases handle the dereferenced value without duplication.
  • hasMinOf and hasMaxOf delegate to isGte and isLte respectively, so they are covered automatically.

Changes

  • baked_in.go: Added pointer dereference guard to isGte, isGt, isLte, isLt, hasLengthOf
  • validator.go: Added nil-pointer skip in traverseField for tags without runValidationWhenNil
  • validator_test.go: Added TestRequiredIfWithPointerAndGte with 4 test cases

Test Plan

  • go build ./... passes
  • go test ./... passes (all existing tests + new test)
  • go vet ./... clean
  • New test TestRequiredIfWithPointerAndGte verifies:
    • Nil *int64 with required_if false: no panic, no error
    • Nil *int64 with required_if true: validation error on required_if
    • *int64=5 with required_if true + gte=2: passes
    • *int64=1 with required_if true + gte=2: fails on gte

Fixes #907

…yground#907)

Comparison validators (isGte, isGt, isLte, isLt, hasLengthOf) panicked
with "Bad field type *T" when called on nil pointer fields. This happened
when a pointer field used required_if alongside a threshold tag (e.g.
required_if=Type special,gte=2) and required_if evaluated to false,
causing the threshold validator to run on the nil pointer.

The fix dereferences pointer fields early in each comparison validator:
nil pointers return false (no threshold is satisfied by absence of value),
non-nil pointers are dereferenced and compared against the threshold using
the existing type-specific logic.

Additionally, traverseField now skips validators that are not marked
runValidationWhenNil when encountering a nil pointer/interface, preventing
unnecessary work and ensuring consistent behavior across all validators.

Fixes go-playground#907
@1solomonwakhungu
1solomonwakhungu requested a review from a team as a code owner July 10, 2026 04:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic Bad field type *int64, when using required_if and omitempty at the same time

1 participant