validator
Go言語の構造体とフィールドバリデーションのための最も人気のあるライブラリ
validatorは、Go言語向けの最も人気のある構造体とフィールドバリデーションライブラリです。タグベースの宣言的なバリデーション、クロスフィールド検証、スライス・配列・マップのディープダイビング、カスタムバリデータ、i18n対応のエラーメッセージなど、包括的な機能を提供します。Ginフレームワークのデフォルトバリデータとしても採用されています。
主な特徴
- タグベースのバリデーション: 構造体タグを使用した直感的なバリデーション定義
- クロスフィールドバリデーション: 複数フィールド間の関係を検証
- ディープダイビング: スライス、配列、マップの要素を再帰的に検証
- カスタムバリデータ: 独自のバリデーションロジックを簡単に追加
- 型安全: Go の型システムを活用した安全なバリデーション
- 高速: ゼロアロケーションの最適化されたパフォーマンス
- i18n対応: 多言語エラーメッセージのサポート
- JSONフィールド名抽出: エラーメッセージにJSONタグ名を使用可能
インストール
# 最新版(v10)
go get github.com/go-playground/validator/v10
# モジュールを使用している場合
go mod download github.com/go-playground/validator/v10
基本的な使い方
パッケージのインポートと初期化
import "github.com/go-playground/validator/v10"
// バリデータインスタンスの作成
var validate *validator.Validate
func init() {
validate = validator.New(validator.WithRequiredStructEnabled())
}
構造体のバリデーション
type User struct {
FirstName string `validate:"required,alpha"`
LastName string `validate:"required,alpha"`
Age uint8 `validate:"gte=0,lte=130"`
Email string `validate:"required,email"`
FavouriteColor string `validate:"iscolor"`
Addresses []*Address `validate:"required,dive,required"`
}
type Address struct {
Street string `validate:"required"`
City string `validate:"required"`
Planet string `validate:"required"`
Phone string `validate:"required,e164"`
}
func main() {
user := &User{
FirstName: "太郎",
LastName: "山田",
Age: 25,
Email: "[email protected]",
FavouriteColor: "#000-",
Addresses: []*Address{
{
Street: "渋谷1-2-3",
City: "東京",
Planet: "地球",
Phone: "+81312345678",
},
},
}
err := validate.Struct(user)
if err != nil {
// バリデーションエラーの処理
for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Namespace()) // User.FirstName
fmt.Println(err.Field()) // FirstName
fmt.Println(err.StructNamespace()) // User.FirstName
fmt.Println(err.StructField()) // FirstName
fmt.Println(err.Tag()) // alpha
fmt.Println(err.ActualTag()) // alpha
fmt.Println(err.Kind()) // string
fmt.Println(err.Type()) // string
fmt.Println(err.Value()) // 太郎
fmt.Println(err.Param()) //
fmt.Println()
}
}
}
組み込みバリデータ
基本的な型チェック
type BasicValidation struct {
// 必須フィールド
Required string `validate:"required"`
// 数値の範囲
Min int `validate:"min=10"`
Max int `validate:"max=100"`
Between int `validate:"gte=10,lte=100"`
// 文字列の長さ
MinLen string `validate:"min=5"`
MaxLen string `validate:"max=10"`
Len string `validate:"len=7"`
// 等価性
Equal string `validate:"eq=test"`
NotEqual string `validate:"ne=test"`
}
ネットワークとフォーマット
type NetworkValidation struct {
// ネットワーク関連
Email string `validate:"email"`
URL string `validate:"url"`
URI string `validate:"uri"`
IP string `validate:"ip"`
IPv4 string `validate:"ipv4"`
IPv6 string `validate:"ipv6"`
CIDR string `validate:"cidr"`
MAC string `validate:"mac"`
// ホスト名とドメイン
Hostname string `validate:"hostname"`
FQDN string `validate:"fqdn"`
Port string `validate:"hostname_port"`
// データフォーマット
UUID string `validate:"uuid"`
Base64 string `validate:"base64"`
JSON string `validate:"json"`
JWT string `validate:"jwt"`
// 暗号通貨アドレス
Bitcoin string `validate:"btc_addr"`
Ethereum string `validate:"eth_addr"`
}
文字列バリデーション
type StringValidation struct {
// 文字種別
Alpha string `validate:"alpha"`
AlphaNum string `validate:"alphanum"`
Numeric string `validate:"numeric"`
Number string `validate:"number"`
Hexadecimal string `validate:"hexadecimal"`
HexColor string `validate:"hexcolor"`
// 大文字小文字
Lowercase string `validate:"lowercase"`
Uppercase string `validate:"uppercase"`
// 含有チェック
Contains string `validate:"contains=test"`
ContainsAny string `validate:"containsany=!@#$"`
ContainsRune string `validate:"containsrune=☺"`
Excludes string `validate:"excludes=test"`
ExcludesAll string `validate:"excludesall=!@#$"`
ExcludesRune string `validate:"excludesrune=☺"`
// 開始・終了
StartsWith string `validate:"startswith=Hello"`
EndsWith string `validate:"endswith=World"`
StartsNotWith string `validate:"startsnotwith=Bad"`
EndsNotWith string `validate:"endsnotwith=Bad"`
}
日付と時刻
type DateTimeValidation struct {
// ISO8601形式
DateTime string `validate:"datetime=2006-01-02"`
// タイムゾーン
Timezone string `validate:"timezone"`
// 実際の日時型での比較も可能
CreatedAt time.Time `validate:"required"`
UpdatedAt time.Time `validate:"gtfield=CreatedAt"`
}
クロスフィールドバリデーション
フィールド間の比較
type CrossFieldExample struct {
// パスワード確認
Password string `validate:"required,min=8"`
PasswordConfirm string `validate:"required,eqfield=Password"`
// 数値の比較
MinPrice float64 `validate:"required"`
MaxPrice float64 `validate:"required,gtfield=MinPrice"`
// 日付の比較
StartDate time.Time `validate:"required"`
EndDate time.Time `validate:"required,gtfield=StartDate"`
// 条件付き必須
ShipToOtherAddress bool `validate:""`
OtherAddress string `validate:"required_if=ShipToOtherAddress true"`
}
複雑な条件付きバリデーション
type ConditionalValidation struct {
// required_if: 特定フィールドが特定値の場合必須
PaymentMethod string `validate:"required,oneof=card cash transfer"`
CardNumber string `validate:"required_if=PaymentMethod card"`
// required_unless: 特定フィールドが特定値でない場合必須
HasAccount bool `validate:""`
Email string `validate:"required_unless=HasAccount true"`
// required_with: 指定フィールドのいずれかが存在する場合必須
FirstName string `validate:""`
LastName string `validate:""`
MiddleName string `validate:"required_with=FirstName LastName"`
// excluded_if: 特定条件で除外
IsAdmin bool `validate:""`
AdminNotes string `validate:"excluded_if=IsAdmin false"`
}
カスタムバリデータ
シンプルなカスタムバリデータ
// 日本の郵便番号バリデータ
func ValidateJPZipCode(fl validator.FieldLevel) bool {
zipCode := fl.Field().String()
// 正規表現でフォーマットチェック
matched, _ := regexp.MatchString(`^\d{3}-\d{4}$`, zipCode)
return matched
}
// 登録
validate.RegisterValidation("jpzipcode", ValidateJPZipCode)
// 使用
type Address struct {
ZipCode string `validate:"required,jpzipcode"`
}
パラメータ付きカスタムバリデータ
// 特定の値リストに含まれるかチェック
func ValidateValueIn(fl validator.FieldLevel) bool {
// パラメータを取得(カンマ区切り)
params := strings.Split(fl.Param(), " ")
value := fl.Field().String()
for _, param := range params {
if value == param {
return true
}
}
return false
}
validate.RegisterValidation("valuein", ValidateValueIn)
// 使用例
type Status struct {
State string `validate:"valuein=active inactive pending"`
}
構造体レベルのバリデーション
// 構造体全体のバリデーション
func UserStructLevelValidation(sl validator.StructLevel) {
user := sl.Current().Interface().(User)
// ビジネスロジックに基づく検証
if user.Age < 18 && user.ParentConsent == false {
sl.ReportError(user.ParentConsent, "ParentConsent", "ParentConsent", "parentconsent", "")
}
// 複数フィールドの組み合わせ検証
if user.Country == "JP" && !strings.HasPrefix(user.Phone, "+81") {
sl.ReportError(user.Phone, "Phone", "Phone", "jpphone", "")
}
}
// 登録
validate.RegisterStructValidation(UserStructLevelValidation, User{})
エラーハンドリング
詳細なエラー情報の取得
err := validate.Struct(user)
if err != nil {
// すべてのエラーを処理
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, fieldError := range validationErrors {
// エラー情報を日本語で構築
switch fieldError.Tag() {
case "required":
fmt.Printf("%sは必須です\n", fieldError.Field())
case "email":
fmt.Printf("%sは有効なメールアドレスではありません\n", fieldError.Field())
case "min":
fmt.Printf("%sは%s文字以上である必要があります\n",
fieldError.Field(), fieldError.Param())
}
}
}
}
JSONフィールド名でのエラー表示
// JSONタグ名を使用する設定
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
type User struct {
FirstName string `json:"first_name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
// エラーメッセージでJSONフィールド名が使用される
// 例: "first_name is required"
高度な使い方
スライスとマップのディープダイビング
type Order struct {
// スライスの各要素を検証
Items []OrderItem `validate:"required,dive,required"`
// マップのキーと値を検証
Metadata map[string]string `validate:"required,dive,keys,alphanum,endkeys,required,max=255"`
// ネストしたスライスの検証
Categories [][]string `validate:"required,dive,dive,required"`
}
type OrderItem struct {
ProductID string `validate:"required,uuid"`
Quantity int `validate:"required,min=1"`
Price float64 `validate:"required,gt=0"`
}
エイリアスの定義
// 複数のバリデーションを1つのタグにまとめる
validate.RegisterAlias("strongpassword", "required,min=8,max=128,containsany=!@#$%^&*")
type Account struct {
Password string `validate:"strongpassword"`
}
翻訳とi18n
import (
"github.com/go-playground/validator/v10"
ja_translations "github.com/go-playground/validator/v10/translations/ja"
"github.com/go-playground/locales/ja"
ut "github.com/go-playground/universal-translator"
)
// 日本語翻訳の設定
ja := ja.New()
uni := ut.New(ja, ja)
trans, _ := uni.GetTranslator("ja")
// 翻訳の登録
ja_translations.RegisterDefaultTranslations(validate, trans)
// カスタム翻訳の追加
validate.RegisterTranslation("jpzipcode", trans, func(ut ut.Translator) error {
return ut.Add("jpzipcode", "{0}は正しい郵便番号形式(123-4567)である必要があります", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("jpzipcode", fe.Field())
return t
})
パフォーマンス最適化
キャッシングの活用
// バリデータの再利用
var (
validate = validator.New()
userValidator = validate.Struct
)
// 構造体のキャッシング
type cachedValidator struct {
validate *validator.Validate
cache sync.Map
}
func (cv *cachedValidator) ValidateStruct(s interface{}) error {
key := reflect.TypeOf(s)
if cached, ok := cv.cache.Load(key); ok {
return cached.(func(interface{}) error)(s)
}
// キャッシュミス時は通常のバリデーション
return cv.validate.Struct(s)
}
ベストプラクティス
- 明確なタグ名: わかりやすいカスタムタグ名を使用
- エラーメッセージの国際化: 多言語対応を考慮
- バリデーションの分離: ビジネスロジックと分離
- パフォーマンス: 頻繁に使用する構造体はキャッシュ
- テスト: カスタムバリデータには必ずテストを作成
Ginフレームワークとの連携
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
// Ginのデフォルトバリデータを置き換え
func setupValidator() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// カスタムバリデータの登録
v.RegisterValidation("jpzipcode", ValidateJPZipCode)
// JSONタグ名の使用
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-" {
return ""
}
return name
})
}
}
// コントローラーでの使用
func CreateUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// バリデーション成功
c.JSON(200, gin.H{"message": "User created"})
}