TL;DR
必須チェックを行う場合は構造体の変数をポインタ型にすることで0や空文字を使ってもバリデーションエラーを返さないようにできます。
type Human struct { Name *string `validate:"required"` Age *int `validate:"required"` }
遭遇した問題
Goで構造体をバリデーションを行っていたとき、Ageに0を入れるとバリデーションエラーを返してしまう問題に遭遇しました。
type Human struct { Name string `validate:"required"` Age int `validate:"required"` } func main() { h := Human{ Name: "Taro", Age: 0, // 意図的に0を入れている } if err := validator.New().Struct(h); err != nil { var ve validator.ValidationErrors if errors.As(err, &ve) { for _, fe := range ve { fmt.Printf("フィールド %s が %s 違反です(値:%v)\n", fe.Field(), fe.Tag(), fe.Value()) // フィールド Age が required 違反です(値:0) } } } }
原因
Goが変数に与える初期値により、validatorが「値が与えられたのか、そうでなかったのか」を判別できなくなるから。
Goの変数の初期値について
Goの変数は宣言時に初期値が与えられます。初期値は型によって異なりますが、以下のようになっています。
- 数値型: 0
- bool型: false
- string型: ""
validatorと初期値の問題
validatorを使って構造体のバリデーションを行うときに、0や""などの値を意図的に入れたとしても、validatorは「値が与えられなかった」と勘違いしてしまいます。
そのため、意図的に0を入れていたのにも関わらず、バリデーションエラーが返ってきたわけです。
解決策
構造体の変数をポインタ型にすることで0や空文字を使ってもバリデーションエラーを返さないようにできます。
type Human struct { Name *string `validate:"required"` Age *int `validate:"required"` }
ポインタにすると値が未設定のとき値はnilになります。そのため、意図的に0や空文字を与えたか、そうでないかを判別できるようになります。
var i int var j *int fmt.Printf("%v\n", i) fmt.Printf("%v\n", j) // 0 // <nil>
そのため以下の場合、Ageに0を与えてもバリデーションエラーは返ってきません。
type Human struct { Name *string `validate:"required"` Age *int `validate:"required"` } func main() { h := Human{ Name: "Taro", Age: 0, } if err := validator.New().Struct(h); err != nil { var ve validator.ValidationErrors if errors.As(err, &ve) { for _, fe := range ve { fmt.Printf("フィールド %s が %s 違反です(値:%v)\n", fe.Field(), fe.Tag(), fe.Value()) } } } }