Skip to content

Validator Validation Library

Official Site: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)

Documentation: validator/README.md at master · go-playground/validator (github.com)

Official Examples: validator/_examples at master · go-playground/validator (github.com)

Benchmarks: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)

Introduction

go-playground/validator implements a struct tag-based value validator with the following unique features:

  • Can use validation tags and custom validators for cross-field and cross-struct validation

  • Slices, arrays, maps, or any multi-dimensional fields can be validated

  • Can dive into validating map keys and values

  • Determines how to handle before validation by its basic type

  • Can handle custom field types

  • Supports alias tags, which allow multiple validations to be mapped to a single tag for easier struct validation definition

  • Can extract custom field names, such as JSON names for display in error messages

  • Custom multi-language error messages

  • Standard default validation component for the gin framework

Installation

powershell
go get github.com/go-playground/validator/v10

Import

go
import "github.com/go-playground/validator/v10"

Tags

The validator has many basic validation tags. All tag corresponding validation functions can be found in the baked_in.go file. The struct tag for the validator is validate.

For example

go
type User {
  age int `validate:"gte=18"` // means greater than or equal to 18 years old
}

You can also modify the default tag through the setTagName method.

Fields

TagDescription
eqcsfieldIn a separate struct, validates that the current field's value equals the field specified by the param value
eqfieldValidates that the current field's value equals the field specified by the param value
fieldcontainsValidates that the current field's value contains the field specified by the param value
fieldexcludesValidates that the current field's value does not contain the field specified by the param value
gtcsfieldIn a separate struct, validates that the current field's value is greater than the field specified by the param value
gtecsfieldIn a separate struct, validates that the current field's value is greater than or equal to the field specified by the param value
gtefieldValidates that the current field's value is greater than or equal to the field specified by the param value
gtfieldValidates that the current field's value is greater than the field specified by the param value
ltcsfieldIn a separate struct, validates that the current field's value is less than the field specified by the param value
ltecsfieldIn a separate struct, validates that the current field's value is less than or equal to the field specified by the param value
ltefieldValidates that the current field's value is less than or equal to the field specified by the param value
ltfieldValidates that the current field's value is less than the field specified by the param value
necsfieldValidates that the current field's value does not equal the field in a separate struct specified by the param value
nefieldValidates that the current field's value does not equal the field specified by the param value

Network

TagDescription
cidrClassless Inter-Domain Routing CIDR
cidrv4Classless Inter-Domain Routing CIDRv4
cidrv6Classless Inter-Domain Routing CIDRv6
datauriData Uniform Resource Identifier
fqdnFully Qualified Domain Name (FQDN)
hostnameHostname RFC 952
hostname_portField validation for <dns>:<port> combination commonly used for socket addresses
hostname_rfc1123Hostname RFC 952
ipInternet Protocol Address IP
ip4_addrInternet Protocol Address IPv4
ip6_addrInternet Protocol Address IPv6
ip_addrInternet Protocol Address IP
ipv4Internet Protocol Address IPv4
ipv6Internet Protocol Address IPv6
macMedia Access Control address, also known as LAN address
tcp4_addrTransmission Control Protocol Address TCP4
tcp6_addrTransmission Control Protocol Address TCPv6
tcp_addrTransmission Control Protocol Address TCP
udp4_addrUser Datagram Protocol Address UDPv4
udp6_addrUser Datagram Protocol Address UDPv6
udp_addrUser Datagram Protocol Address UDP
unix_addrUnix domain socket endpoint address
uriUniform Resource Identifier
urlUniform Resource Locator
url_encodedUniform Resource Identifier encoding
urn_rfc2141RFC 2141 Uniform Resource Name

Strings

TagDescription
alphaValidates that the current field's value is a valid alphabetic string
alphanumValidates that the current field's value is a valid alphanumeric string
alphanumunicodeValidates that the current field's value is a valid alphanumeric unicode string
alphaunicodeValidates that the current field's value is a valid alphabetic unicode string
asciiValidates that the field's value is valid ASCII characters
booleanValidates that the current field's value is a valid boolean or can be safely converted to a boolean
containsValidates that the field's value contains the text specified in the param
containsanyValidates that the field's value contains any of the characters specified in the param
containsruneValidates that the field's value contains the rune specified in the param
endsnotwithValidates that the field's value does not end with the text specified in the param
endswithValidates that the field's value ends with the text specified in the param
excludesValidates that the field's value does not contain the text specified in the param
excludesallValidates that the field's value does not contain any of the characters specified in the param
excludesruneValidates that the field's value does not contain the rune specified in the param
lowercaseValidates that the current field's value is a lowercase string
multibyteValidates that the field's value has multi-byte characters
numberValidates that the current field's value is a valid number
numericValidates that the current field's value is a valid numeric value
printasciiValidates that the field's value is valid printable ASCII characters
startsnotwithValidates that the field's value does not start with the text specified in the param
startswithValidates that the field's value starts with the text specified in the param
uppercaseValidates that the current field's value is an uppercase string

Formatting

TagDescription
base64Base64 string
base64urlBase64URL string
bicValidates that the current field's value is a valid BIC code (SWIFT code) as defined in ISO 9362
bcp47_language_tagValidates that the current field's value is a BCP47 language tag
btc_addrValidates that the field's value is a valid BTC address
btc_addr_bech32Validates that the field's value is a valid bech32 BTC address
credit_cardValidates that the current field's value is a valid credit card number
datetimeValidates that the current field's value is a valid datetime string
e164Validates that the current field's value is a valid e.164 formatted phone number
emailValidates that the current field's value is a valid email address
eth_addrValidates that the field's value is a valid Ethereum address
hexadecimalValidates that the current field's value is a valid hexadecimal
hexcolorValidates that the current field's value is a valid hex color
hslValidates that the current field's value is a valid HSL color
hslaValidates that the current field's value is a valid HSLA color
htmlValidates that the current field's value is valid HTML
html_encodedValidates that the current field's value is valid HTML encoded
isbnValidates that the field's value is a valid v10 or v13 ISBN (International Standard Book Number)
isbn10Validates that the field's value is a valid v10 ISBN
isbn13Validates that the field's value is a valid v13 ISBN
iso3166_1_alpha2Validates that the current field's value is a valid iso3166-1 alpha-2 country code
iso3166_1_alpha3Validates that the current field's value is a valid iso3166-1 alpha-3 country code
iso3166_1_alpha_numericValidates that the current field's value is a valid iso3166-1 alphanumeric country code
iso3166_2Validates that the current field's value is a valid country region code (ISO 3166-2)
iso4217Validates that the current field's value is a valid currency code (ISO 4217)
jsonValidates that the current field's value is a valid JSON string
jwtValidates that the current field's value is a valid JWT string
latitudeValidates that the field's value is a valid latitude coordinate
longitudeValidates that the field's value is a valid longitude coordinate
postcode_iso3166_alpha2Validates by the value of the country code in iso 3166 alpha 2
postcode_iso3166_alpha2_fieldValidates by field, which represents the country code value in iso 3166 alpha 2
rgbValidates that the current field's value is a valid RGB color
rgbaValidates that the current field's value is a valid RGBA color
ssnValidates that the field's value is a valid SSN
timezoneValidates that the current field's value is a valid timezone string
uuidValidates that the field's value is a valid UUID of any version
uuid3Validates that the field's value is a valid UUID v3
uuid3_rfc4122Validates that the field's value is a valid RFC4122 v3 UUID
uuid4Validates that the field's value is a valid v4 UUID
uuid4_rfc4122Validates that the field's value is a valid RFC4122 v4 UUID
uuid5Validates that the field's value is a valid v5 UUID
uuid5_rfc4122Validates that the field's value is a valid RFC4122 v5 UUID
uuid_rfc4122Validates that the field's value is a valid RFC4122 UUID of any version
md4Validates that the field's value is a valid MD4
md5Validates that the field's value is a valid MD5
sha256Validates that the field's value is a valid SHA256
sha384Validates that the field's value is a valid SHA384
sha512Validates that the field's value is a valid SHA512
ripemd128Validates that the field's value is a valid RIPEMD128
ripemd160Validates that the field's value is a valid RIPEMD160
tiger128Validates that the field's value is a valid TIGER128
tiger160Validates that the field's value is a valid TIGER160
tiger192Validates that the field's value is a valid TIGER192
semverValidates that the current field's value is a valid semver version as defined in Semantic Versioning 2.0.0
ulidValidates that the field's value is a valid ULID

Comparison

TagDescription
eqEquals
gtGreater than
gteGreater than or equal
ltLess than
lteLess than or equal
neNot equals

Other

TagDescription
dirFile directory
fileFile path
isdefaultValidates that the current field's value is the default static value
lenField length
maxMaximum value
minMinimum value
oneofWhether it is one of the enumerated values
omitemptyIf the field is not set, ignore the field
requiredRequired value
required_ifField must be present and not empty only when all other specified fields equal the specified value
required_unlessField must be present and not empty unless all other specified fields equal the specified value
required_withField must be present and not empty when any of the specified fields exist
required_with_allField must be present and not empty when all specified fields exist
required_withoutField must be present and not empty when any of the specified fields do not exist
required_without_allField must be present and not empty when all specified fields do not exist
excluded_ifField can be absent or empty only when all other specified fields equal the specified value
excluded_unlessField can be absent or empty unless all other specified fields equal the specified value
excluded_withField can be absent or empty when any of the specified fields exist
excluded_with_allField can be absent or empty when all specified fields exist
excluded_withoutField can be absent or empty when any of the specified fields do not exist
excluded_without_allField can be absent or empty when all specified fields do not exist
uniqueValidates that each arr, map, slice value is unique

Aliases

TagDescription
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

Operators

TagDescriptionHex
,AND operator, use multiple validation tags, all conditions must be satisfied, no spaces between commas0x2c
|OR operator, use multiple validation tags, but only one needs to be satisfied0x7c
-Skip validation for this field0x2d
=Parameter matching symbol0x3d

TIP

When matching operators during field validation, you need to replace them with utf8 hexadecimal representation, for example

go
filed string `validate:"contains=0x2c"`

Usage

The following will introduce some basic usage of Validator with code examples.

Singleton

go
var validate *validator.Validate

When using, the official recommendation is to have only one validator instance throughout the program's lifecycle, which will help cache some data.

Create Validator

When using Validator alone without integrating other frameworks, we need to manually create the validator.

go
validate = validator.New()

Struct Validation

go
func (v *Validate) Struct(s interface{}) error

The Struct method is used to validate all public fields of a struct. By default, it automatically performs nested struct validation. When an invalid value is passed or the value is nil, it returns InvalidValidationError. If validation fails, it returns ValidationErrors.

Example

go
package validate

import (
  "fmt"
  "github.com/go-playground/validator/v10"
  "testing"
)

type User struct {
  Name    string `validate:"contains=jack"` // Name contains jack
  Age     int    `validate:"gte=18"`        // Greater than or equal to 18 years old
  Address string `validate:"endswith=市"`    // Ends with 市 (city)
}

func TestStruct(t *testing.T) {
  validate := validator.New()
  user := User{
    Name:    "jacklove",
    Age:     17,
    Address: "滔博市",
  }
  err := validate.Struct(user)
  for _, err := range err.(validator.ValidationErrors) {
    fmt.Println(err.Namespace()) // Namespace
    fmt.Println(err.Field())
    fmt.Println(err.StructNamespace())
    fmt.Println(err.StructField())
    fmt.Println(err.Tag())
    fmt.Println(err.ActualTag())
    fmt.Println(err.Kind())
    fmt.Println(err.Type())
    fmt.Println(err.Value())
    fmt.Println(err.Param())
    fmt.Println()
  }
  fmt.Println(err)
}

Output

User.Age
Age
User.Age
Age
gte
gte
int
int
17
18

Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tag

Map Validation

go
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}

Validates key-value pairs through a map tag.

Example

go
func TestMap(t *testing.T) {
   user := map[string]interface{}{
      "name":    "jak",
      "age":     17,
      "address": "滔博市",
   }
   rules := map[string]interface{}{
      "name":    "contains=jacklove",
      "age":     "gte=18",
      "address": "endswith=市",
   }

   validate := validator.New()

   validateMap := validate.ValidateMap(user, rules)
   fmt.Println(validateMap)
}

Output

map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]

Slice Validation

Validates string slices. Tags before dive validate the slice, tags after dive validate the values in the slice. Nested slices follow the same principle - use as many dive tags as there are dimensions.

go
func TestSlice1(t *testing.T) {
  list := []string{"jack", "mike", "lisa", "golang"}
  err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // Slice max length is 5, elements must contain character 'a', and min length is 5
  fmt.Println(err)
}

Output

Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '[1]' Error:Field validation for '[1]' failed on the 'contains' tag
Key: '[2]' Error:Field validation for '[2]' failed on the 'min' tag

Validate each user in a slice with struct validation

go
func TestSlice(t *testing.T) {
   userList := make([]User, 0)
   user := User{
      Name:    "jacklove",
      Age:     17,
      Address: "滔博市",
   }
   userList = append(userList, user)
   err := validator.New().Var(userList, "dive") // "dive" means deep validation, automatically performs struct validation when element is a struct
   fmt.Println(err)
}

Output

Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tag

Variable Validation

Simple and easy to understand, no need for excessive explanation

Example 1

go
func TestVar(t *testing.T) {
   name := "jack"
   err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // Max length 5, min length 1, contains letter 'a', ends with letter 'l'
   fmt.Println(err)
}

Output

Key: '' Error:Field validation for '' failed on the 'endswith' tag

Example 2

go
func TestVar1(t *testing.T) {
   age := 18
   err := validator.New().Var(age, "gte=19")
   fmt.Println(err)
}

Output

Key: '' Error:Field validation for '' failed on the 'gte' tag

TIP

The Var method can validate types including structs, variables, slices, and maps. Use it reasonably combined with the dive tag.

Field Validation

Field validation parameters are no longer basic types, but struct field names. They can be the struct's own field names or nested struct field names.

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SecondPassword"` // Validates if two password inputs are equal
   SecondPassword string
}

type RegisterUser struct {
   Username string `validate:"necsfield=Password.FirstPassword"` // For security during registration, prohibit password matching username
   Password Password
}

func TestCrossStructFieldValidate(t *testing.T) {
   validate = validator.New()
   // Failure
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gopher",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gophers",
      },
   }))
   // Success
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gophers",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gopher",
      },
   }))
}

Output

Key: 'RegisterUser.Username' Error:Field validation for 'Username' failed on the 'necsfield' tag
Key: 'RegisterUser.Password.FirstPassword' Error:Field validation for 'FirstPassword' failed on the 'eqfield' tag
<nil>

WARNING

When using field validation, if the field or struct specified as a Tag parameter does not exist, it will be directly judged as validation failure. For example:

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
   SecondPassword string
}

For such spelling errors, it's hard to detect, and during validation it will only show as validation failure. This needs special attention.

Advanced

Next, we'll explain some advanced usage techniques and more custom operations.

Custom Aliases

Sometimes, a field has many validation tags. When you want to reuse them on another field, you might directly copy and paste, but this isn't the best solution. A better approach is to register aliases to improve reusability. See the example below:

go
var validate *validator.Validate

const PERSON_NAME_RULES = "max=10,min=1,contains=jack"

func TestAlias(t *testing.T) {
  validate = validator.New()
    // Register alias
  validate.RegisterAlias("namerules", PERSON_NAME_RULES)
  type person struct {
    FirstName string `validate:"namerules"` // Use alias
    LastName  string `validate:"namerules"`
  }

  err := validate.Struct(person{
    FirstName: "",
    LastName:  "",
  })

  fmt.Println(err)
}

Output

go
Key: 'person.FirstName' Error:Field validation for 'FirstName' failed on the 'namerules' tag
Key: 'person.LastName' Error:Field validation for 'LastName' failed on the 'namerules' tag

Custom Validation Functions

Although the component's built-in validation tags are sufficient for basic scenarios, sometimes for special requirements you must define your own logic. Validator provides related APIs for customizing validation functions. Let's look at an example:

go
func TestCustomValidate(t *testing.T) {
   validate = validator.New()
   fmt.Println(validate.RegisterValidation("is666", is666))
   type Example struct {
      Name string `validate:"is666"`
   }
   fmt.Println(validate.Struct(Example{Name: "777"}))
   fmt.Println(validate.Struct(Example{Name: "666"}))
}

func is666(fl validator.FieldLevel) bool {
   return fl.Field().String() == "666"
}

Created a function to check if the field value equals "666", and its corresponding tag is is666. Output is as follows:

go
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tag

TIP

If a registered tag already exists, it will be overwritten by the existing one. In other words, you can "rewrite" the default tag validation logic.

Custom Type Validation Functions

Type validation functions are specifically for certain types, usually for non-basic types. Similarly, you can also override the default basic type validation. See the example below:

go
type Address struct {
  name string
}

func TestCustomTypeValidate(t *testing.T) {
  validate = validator.New()
  validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // Register type validation function and corresponding type
  type Example struct {
    Address Address `validate:"required"`
  }
  fmt.Println(validate.Struct(Example{Address: Address{name: ""}}))
  fmt.Println(validate.Struct(Example{Address: Address{name: "cn"}}))
}

func ValidateAddress(value reflect.Value) interface{} {
  if address, ok := value.Interface().(Address); ok {
    // Error handling
    if address.name == "" {
      return address.name
    }

    return value // Returning the field means validation passed
  }
  return nil
}

Output

go
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>

TIP

Registering multiple types to one function follows the same principle.

Custom Struct Level Validation Functions

The difference with struct level validation functions is that other functions' parameters are fields, while this function's parameter is the struct itself. See the example below:

go
type People struct {
   FirstName string
   LastName  string
}

func TestCustomStructLevel(t *testing.T) {
   validate = validator.New()
   validate.RegisterStructValidation(PeopleValidate, People{}) // Same as type registration, you can pass in more than one struct type
   err := validate.Struct(People{
      FirstName: "",
      LastName:  "",
   })
   fmt.Println(err)
}

func PeopleValidate(sl validator.StructLevel) {
   people := sl.Current().Interface().(People)

   if people.FirstName == "" || people.LastName == "" {
      sl.ReportError(people.FirstName, "FirstName", "FirstName", "", "")
      sl.ReportError(people.FirstName, "LastName", "LastName", "", "")
   }
}

Output

Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tag

Multi-language

Translator component

go get github.com/go-playground/universal-translator

Locale component

go get github.com/go-playground/locales

The validator's default language is English. When developing projects, we might need to use more than one language. In this case, we need to use the internationalization multi-language component. See the example below:

go
import (
  "fmt"
  "github.com/go-playground/locales/zh"
  ut "github.com/go-playground/universal-translator"
  "github.com/go-playground/validator/v10"
  zh_trans "github.com/go-playground/validator/v10/translations/zh"
  "reflect"
  "testing"
)

type User struct {
   Name    string `validate:"contains=jack"` // Name contains jack
   Age     int    `validate:"gte=18"`        // Greater than or equal to 18 years old
   Address string `validate:"endswith=市"`    // Ends with 市 (city)
}

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
)

func TestTranslate(t *testing.T) {
   zh := zh.New()
   // First is fallback, subsequent are supported languages, can have multiple
   uni = ut.New(zh, zh)
   // The language here is usually obtained from the Accept-Language header in HTTP requests
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) // Register default translator
   }
   err := validate.Struct(User{
      Name:    "",
      Age:     0,
      Address: "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}

Output

map[User.Address:Address must end with text '市' User.Age:Age must be greater than or equal to 18 User.Name:Name must contain text 'jack']

You can also translate each error individually

go
for _, fieldError := range err.(validator.ValidationErrors) {
   fmt.Println(fieldError.Translate(trans))
}

Output

Name must contain text 'jack'
Age must be greater than or equal to 18
Address must end with text '市'

You can see the return value is a map. Basic error message translation is achieved, but it's not yet ready for production use. We need to further beautify the error messages for better integration with customers or frontend.

go
type User struct {

   Name    string `validate:"contains=jack" label:"姓名"` // Name contains jack
   Age     int    `validate:"gte=18" label:"年龄"`        // Greater than or equal to 18 years old
   Address string `validate:"endswith=市" label:"地址"`    // Ends with 市 (city)
   Sex     string `validate:"required" label:"性别"`
}

First, customize the label tag. Its value is the Chinese name of the field. Then register a TagNameFunc with the validator. Its role is to get the field name or replace the original name. The comment on the Field() string method in the errors.go file says: "Field name with label tag takes precedence over the field's actual name". So when errors occur, we can use the custom Chinese name to replace the English word. TagNameFunc is as follows:

go
// We added a custom tag, which is used to give struct fields Chinese names, replacing the original field names
func CustomTagNameFunc(field reflect.StructField) string {
   label := field.Tag.Get("label")
   if len(label) == 0 {
      return field.Name
   }
   return label
}

Then register

go
validate.RegisterTagNameFunc(CustomTagNameFunc)

Execute again, output

姓名 must contain text 'jack'
年龄 must be greater than or equal to 18
地址 must end with text '市'

But this is still not enough. It's not yet suitable as error information returned to the frontend. We need to format the information into JSON or any format suitable for message transmission. You might think of directly serializing the map to JSON, which is a solution, but you might get the following result:

json
{
  "User.地址": "地址 must end with text '市'",
  "User.姓名": "姓名 must contain text 'back'",
  "User.年龄": "年龄 must be greater than or equal to 18",
  "User.性别": "性别 is required"
}

By processing the map's key values, get the following result:

json
{
  "地址": "地址 must end with text '市'",
  "姓名": "姓名 must contain text 'back'",
  "年龄": "年龄 must be greater than or equal to 18",
  "性别": "性别 is required"
}

However, it's not recommended to return this kind of information to the frontend. We can process it into a string as the message return:

姓名 must contain text 'back', 年龄 must be greater than or equal to 18, 地址 must end with text '市', 性别 is required,

Complete Code

go
import (
   "fmt"
   "github.com/go-playground/locales/zh"
   ut "github.com/go-playground/universal-translator"
   "github.com/go-playground/validator/v10"
   zh_trans "github.com/go-playground/validator/v10/translations/zh"
   "reflect"
   "strings"
   "testing"
)

type User struct {
   Name    string `validate:"contains=back" label:"姓名"` // Name contains jack
   Age     int    `validate:"gte=18" label:"年龄"`        // Greater than or equal to 18 years old
   Address string `validate:"endswith=市" label:"地址"`    // Ends with 市 (city)
   Sex     string `validate:"required" label:"性别"`
}

var (
   uni      *ut.UniversalTranslator
   validate *validator.Validate
)

// We added a custom tag, which is used to give struct fields Chinese names, replacing the original field names
func CustomTagNameFunc(field reflect.StructField) string {
   label := field.Tag.Get("label")
   if len(label) == 0 {
      return field.Name
   }
   return label
}

func TestTranslate(t *testing.T) {
   zh := zh.New()
   uni = ut.New(zh, zh)
   // The language here is usually obtained from the Accept-Language header in HTTP requests
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) // Register default translator
   }
   validate.RegisterTagNameFunc(CustomTagNameFunc)
   err := validate.Struct(User{
      Name:    "",
      Age:     0,
      Address: "",
   })
   translate := errInfoFormat(err.(validator.ValidationErrors), trans)
   fmt.Println(translate)
}

func errInfoFormat(errors validator.ValidationErrors, trans ut.Translator) string {
   builder := strings.Builder{}
   for _, err := range errors {
      builder.WriteString(err.Translate(trans))
      builder.WriteString(", ")
   }
   return builder.String()
}

Finally, if you feel the error messages are too cold and want them more humanized, you can rewrite the error messages for specific tags. This requires using the RegisterTranslation method, and also requires two types of functions: RegisterTranslationsFunc responsible for registering the translation template for the corresponding tag, and TranslationFunc responsible for processing the template to get the final translation content. Here's an example using required:

go
func requiredOverrideRegister(ut ut.Translator) error { // This function's role is to register the translation template
  return ut.Add("required", "{} is a required field", true) // {} is a placeholder, true represents whether to override existing template
}

func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // This function's role is responsible for translating content
  t, _ := ut.T("required", fe.Field()) // Parameters can be multiple, depending on how many placeholders the registered tag template has
  return t
}

Finally register

go
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)

Result

姓名 must contain text 'back', 年龄 must be greater than or equal to 18, 地址 must end with text '市', 性别 is a required field,

Language Files

In fact, registering one by one with code is very tedious. universal-translator provides a way to translate through JSON configuration files: universal-translator/examples/full-with-files at master · go-playground/universal-translator (github.com)

go
func TestFilei18n(t *testing.T) {
   validate = validator.New()
   zh := zh.New()
   universalTranslator := ut.New(zh, zh)
   translator, _ := universalTranslator.GetTranslator(zh.Locale())
   zh_trans.RegisterDefaultTranslations(validate, translator)
   er := universalTranslator.Import(ut.FormatJSON, "./zh.json") // Recommended to import after registration, so it can override existing tags
   if er != nil {
      log.Fatal(er)
   }
   type Gopher struct {
      Language string `validate:"required"`
   }

   err := validate.Struct(Gopher{
      "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}

JSON file

json
[
  {
    "locale": "zh",
    "key": "required",
    "trans": "This is a very important field {0}, you must fill it in",
    "override": true
  }
]

Output

map[Gopher.Language:This is a very important field Language, you must fill it in]

TIP

universal-translator has many pitfalls when using. If you want to override the existing Tag, type and rule can both be left empty because the original configuration doesn't have them either. It's best to keep consistency. Whatever type you fill in will be added to the corresponding map. If it's Cardinal or other type and rule is configured with one or similar, then you need to make corresponding configurations locally to use it normally, otherwise it will report an error.

Golang by www.golangdev.cn edit