Skip to content

Commit f5f02dc

Browse files
authored
feat: Add support for omitting empty and zero values in validation (including nil pointer and empty content of pointer) (#1289)
## Fixes Or Enhances **Add the `omitzero` Tag:** - It allows you to ignore the subsequent validations if the value of a field is empty - If the field is a pointer and the pointer is nil, ignore the subsequent validations - If the field is a pointer and the pointer is not nil, but the content value of the pointer is zero, the subsequent validations will be ignored **Make sure that you've checked the boxes below before you submit PR:** - [x] Tests exist or have been written that cover this particular change. @go-playground/validator-maintainers
1 parent c171f2d commit f5f02dc

5 files changed

+89
-0
lines changed

baked_in.go

+15
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ var (
5050
keysTag: {},
5151
endKeysTag: {},
5252
structOnlyTag: {},
53+
omitzero: {},
5354
omitempty: {},
5455
omitnil: {},
5556
skipValidationTag: {},
@@ -1797,6 +1798,20 @@ func hasValue(fl FieldLevel) bool {
17971798
}
17981799
}
17991800

1801+
// hasNotZeroValue is the validation function for validating if the current field's value is not the zero value for its type.
1802+
func hasNotZeroValue(fl FieldLevel) bool {
1803+
field := fl.Field()
1804+
switch field.Kind() {
1805+
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
1806+
return !field.IsNil()
1807+
default:
1808+
if fl.(*validate).fldIsPointer && field.Interface() != nil {
1809+
return !field.IsZero()
1810+
}
1811+
return field.IsValid() && !field.IsZero()
1812+
}
1813+
}
1814+
18001815
// requireCheckFieldKind is a func for check field kind
18011816
func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool {
18021817
field := fl.Field()

cache.go

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
typeKeys
2222
typeEndKeys
2323
typeOmitNil
24+
typeOmitZero
2425
)
2526

2627
const (
@@ -249,6 +250,10 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
249250
}
250251
return
251252

253+
case omitzero:
254+
current.typeof = typeOmitZero
255+
continue
256+
252257
case omitempty:
253258
current.typeof = typeOmitEmpty
254259
continue

validator.go

+17
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
117117
return
118118
}
119119

120+
if ct.typeof == typeOmitZero {
121+
return
122+
}
123+
120124
if ct.hasTag {
121125
if kind == reflect.Invalid {
122126
v.str1 = string(append(ns, cf.altName...))
@@ -238,6 +242,19 @@ OUTER:
238242
ct = ct.next
239243
continue
240244

245+
case typeOmitZero:
246+
v.slflParent = parent
247+
v.flField = current
248+
v.cf = cf
249+
v.ct = ct
250+
251+
if !hasNotZeroValue(v) {
252+
return
253+
}
254+
255+
ct = ct.next
256+
continue
257+
241258
case typeOmitNil:
242259
v.slflParent = parent
243260
v.flField = current

validator_instance.go

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
tagKeySeparator = "="
2222
structOnlyTag = "structonly"
2323
noStructLevelTag = "nostructlevel"
24+
omitzero = "omitzero"
2425
omitempty = "omitempty"
2526
omitnil = "omitnil"
2627
isdefault = "isdefault"

validator_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -14022,6 +14022,57 @@ func TestOmitNilAndRequired(t *testing.T) {
1402214022
})
1402314023
}
1402414024

14025+
func TestOmitZero(t *testing.T) {
14026+
type (
14027+
OmitEmpty struct {
14028+
Str string `validate:"omitempty,min=10"`
14029+
StrPtr *string `validate:"omitempty,min=10"`
14030+
}
14031+
OmitZero struct {
14032+
Str string `validate:"omitzero,min=10"`
14033+
StrPtr *string `validate:"omitzero,min=10"`
14034+
}
14035+
)
14036+
14037+
var (
14038+
validate = New()
14039+
valid = "this is the long string to pass the validation rule"
14040+
empty = ""
14041+
)
14042+
14043+
t.Run("compare using valid data", func(t *testing.T) {
14044+
err1 := validate.Struct(OmitEmpty{Str: valid, StrPtr: &valid})
14045+
err2 := validate.Struct(OmitZero{Str: valid, StrPtr: &valid})
14046+
14047+
Equal(t, err1, nil)
14048+
Equal(t, err2, nil)
14049+
})
14050+
14051+
t.Run("compare fully empty omitempty and omitzero", func(t *testing.T) {
14052+
err1 := validate.Struct(OmitEmpty{})
14053+
err2 := validate.Struct(OmitZero{})
14054+
14055+
Equal(t, err1, nil)
14056+
Equal(t, err2, nil)
14057+
})
14058+
14059+
t.Run("compare with zero value", func(t *testing.T) {
14060+
err1 := validate.Struct(OmitEmpty{Str: "", StrPtr: nil})
14061+
err2 := validate.Struct(OmitZero{Str: "", StrPtr: nil})
14062+
14063+
Equal(t, err1, nil)
14064+
Equal(t, err2, nil)
14065+
})
14066+
14067+
t.Run("compare with empty value", func(t *testing.T) {
14068+
err1 := validate.Struct(OmitEmpty{Str: empty, StrPtr: &empty})
14069+
err2 := validate.Struct(OmitZero{Str: empty, StrPtr: &empty})
14070+
14071+
AssertError(t, err1, "OmitEmpty.StrPtr", "OmitEmpty.StrPtr", "StrPtr", "StrPtr", "min")
14072+
Equal(t, err2, nil)
14073+
})
14074+
}
14075+
1402514076
func TestPrivateFieldsStruct(t *testing.T) {
1402614077
type tc struct {
1402714078
stct interface{}

0 commit comments

Comments
 (0)