🦔

Goの`int` で `0` を送ると バリデーション`required` に引っかかる件

に公開

今回は Gin(Go)で API バリデーションをしていて、int 型のフィールドに 0 を送ったら required バリデーションに引っかかるという現象に遭遇したので、記録として残します。


結論

問題内容

int 型の 0 が required に引っかかる

解決

*int(ポインタ型)に変更する

問題の発端

とある構造体で以下のようなバリデーションを定義していました。

バックエンド側でのDTOの定義はこれ。

type UpdateCounterDTO struct {
	CounterName string `json:"counter_name" binding:"max=300"`
	Count       int    `json:"count" binding:"required,min=0,max=10000"`
}

実際のクライアント側でのリクエストのペイロードは以下。

{count: 0}

そして、リクエストのレスポンスは以下。

{
    "error": "bad_request",
    "message": "Bad requestKey: 'UpdateCounterDTO.Count' Error:Field validation for 'Count' failed on the 'required' tag"
}

なぜでしょうか?
countで0を送ってるのに required に引っかかってる!?
(ginさん、0は0でnilじゃないんだよ!!)

原因

Goでは、int のゼロ値は 0です。
そして Gin のバリデーションライブラリ(go-playground/validator)は、required の評価にゼロ値かどうかを使っています。

つまり int 型のフィールドに 0 を入れても、「値が設定されていない(=ゼロ値)」と見なされてしまいます。

値 Goの評価 requiredの評価
0 正常な int 値 ゼロ値なので「未設定」とみなされる
解決法:ポインタ型(*int)を使う
構造体を以下のように修正します。

type UpdateCounterDTO struct { 
    CounterName string `json:"counter_name" binding:"max=300"`
	Count        *int   `json:"count" binding:"required,min=0,max=10000"`
}

こうすることで、nil(値が送られていない)と 0(明示的に送られた)を区別できるようになり、required バリデーションが正しく動作します。

補足:なぜポインタだと通るのか

ポインタ型にすることで、以下のような動作になります。

ペイロード Goでの値 required の評価
{} Count = nil(全機能の上位概念) 弾かれる(未設定)
{"count": 0} Count = &0 通る(明示的な 0)

同じことでハマる人、きっと多いはずなので、どなたかの助けになれば幸いです。

Discussion