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)
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
ginframework
Installation
go get github.com/go-playground/validator/v10Import
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
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
| Tag | Description |
|---|---|
eqcsfield | In a separate struct, validates that the current field's value equals the field specified by the param value |
eqfield | Validates that the current field's value equals the field specified by the param value |
fieldcontains | Validates that the current field's value contains the field specified by the param value |
fieldexcludes | Validates that the current field's value does not contain the field specified by the param value |
gtcsfield | In a separate struct, validates that the current field's value is greater than the field specified by the param value |
gtecsfield | In a separate struct, validates that the current field's value is greater than or equal to the field specified by the param value |
gtefield | Validates that the current field's value is greater than or equal to the field specified by the param value |
gtfield | Validates that the current field's value is greater than the field specified by the param value |
ltcsfield | In a separate struct, validates that the current field's value is less than the field specified by the param value |
ltecsfield | In a separate struct, validates that the current field's value is less than or equal to the field specified by the param value |
ltefield | Validates that the current field's value is less than or equal to the field specified by the param value |
ltfield | Validates that the current field's value is less than the field specified by the param value |
necsfield | Validates that the current field's value does not equal the field in a separate struct specified by the param value |
nefield | Validates that the current field's value does not equal the field specified by the param value |
Network
| Tag | Description |
|---|---|
cidr | Classless Inter-Domain Routing CIDR |
cidrv4 | Classless Inter-Domain Routing CIDRv4 |
cidrv6 | Classless Inter-Domain Routing CIDRv6 |
datauri | Data Uniform Resource Identifier |
fqdn | Fully Qualified Domain Name (FQDN) |
hostname | Hostname RFC 952 |
hostname_port | Field validation for <dns>:<port> combination commonly used for socket addresses |
hostname_rfc1123 | Hostname RFC 952 |
ip | Internet Protocol Address IP |
ip4_addr | Internet Protocol Address IPv4 |
ip6_addr | Internet Protocol Address IPv6 |
ip_addr | Internet Protocol Address IP |
ipv4 | Internet Protocol Address IPv4 |
ipv6 | Internet Protocol Address IPv6 |
mac | Media Access Control address, also known as LAN address |
tcp4_addr | Transmission Control Protocol Address TCP4 |
tcp6_addr | Transmission Control Protocol Address TCPv6 |
tcp_addr | Transmission Control Protocol Address TCP |
udp4_addr | User Datagram Protocol Address UDPv4 |
udp6_addr | User Datagram Protocol Address UDPv6 |
udp_addr | User Datagram Protocol Address UDP |
unix_addr | Unix domain socket endpoint address |
uri | Uniform Resource Identifier |
url | Uniform Resource Locator |
url_encoded | Uniform Resource Identifier encoding |
urn_rfc2141 | RFC 2141 Uniform Resource Name |
Strings
| Tag | Description |
|---|---|
alpha | Validates that the current field's value is a valid alphabetic string |
alphanum | Validates that the current field's value is a valid alphanumeric string |
alphanumunicode | Validates that the current field's value is a valid alphanumeric unicode string |
alphaunicode | Validates that the current field's value is a valid alphabetic unicode string |
ascii | Validates that the field's value is valid ASCII characters |
boolean | Validates that the current field's value is a valid boolean or can be safely converted to a boolean |
contains | Validates that the field's value contains the text specified in the param |
containsany | Validates that the field's value contains any of the characters specified in the param |
containsrune | Validates that the field's value contains the rune specified in the param |
endsnotwith | Validates that the field's value does not end with the text specified in the param |
endswith | Validates that the field's value ends with the text specified in the param |
excludes | Validates that the field's value does not contain the text specified in the param |
excludesall | Validates that the field's value does not contain any of the characters specified in the param |
excludesrune | Validates that the field's value does not contain the rune specified in the param |
lowercase | Validates that the current field's value is a lowercase string |
multibyte | Validates that the field's value has multi-byte characters |
number | Validates that the current field's value is a valid number |
numeric | Validates that the current field's value is a valid numeric value |
printascii | Validates that the field's value is valid printable ASCII characters |
startsnotwith | Validates that the field's value does not start with the text specified in the param |
startswith | Validates that the field's value starts with the text specified in the param |
uppercase | Validates that the current field's value is an uppercase string |
Formatting
| Tag | Description |
|---|---|
base64 | Base64 string |
base64url | Base64URL string |
bic | Validates that the current field's value is a valid BIC code (SWIFT code) as defined in ISO 9362 |
bcp47_language_tag | Validates that the current field's value is a BCP47 language tag |
btc_addr | Validates that the field's value is a valid BTC address |
btc_addr_bech32 | Validates that the field's value is a valid bech32 BTC address |
credit_card | Validates that the current field's value is a valid credit card number |
datetime | Validates that the current field's value is a valid datetime string |
e164 | Validates that the current field's value is a valid e.164 formatted phone number |
email | Validates that the current field's value is a valid email address |
eth_addr | Validates that the field's value is a valid Ethereum address |
hexadecimal | Validates that the current field's value is a valid hexadecimal |
hexcolor | Validates that the current field's value is a valid hex color |
hsl | Validates that the current field's value is a valid HSL color |
hsla | Validates that the current field's value is a valid HSLA color |
html | Validates that the current field's value is valid HTML |
html_encoded | Validates that the current field's value is valid HTML encoded |
isbn | Validates that the field's value is a valid v10 or v13 ISBN (International Standard Book Number) |
isbn10 | Validates that the field's value is a valid v10 ISBN |
isbn13 | Validates that the field's value is a valid v13 ISBN |
iso3166_1_alpha2 | Validates that the current field's value is a valid iso3166-1 alpha-2 country code |
iso3166_1_alpha3 | Validates that the current field's value is a valid iso3166-1 alpha-3 country code |
iso3166_1_alpha_numeric | Validates that the current field's value is a valid iso3166-1 alphanumeric country code |
iso3166_2 | Validates that the current field's value is a valid country region code (ISO 3166-2) |
iso4217 | Validates that the current field's value is a valid currency code (ISO 4217) |
json | Validates that the current field's value is a valid JSON string |
jwt | Validates that the current field's value is a valid JWT string |
latitude | Validates that the field's value is a valid latitude coordinate |
longitude | Validates that the field's value is a valid longitude coordinate |
postcode_iso3166_alpha2 | Validates by the value of the country code in iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Validates by field, which represents the country code value in iso 3166 alpha 2 |
rgb | Validates that the current field's value is a valid RGB color |
rgba | Validates that the current field's value is a valid RGBA color |
ssn | Validates that the field's value is a valid SSN |
timezone | Validates that the current field's value is a valid timezone string |
uuid | Validates that the field's value is a valid UUID of any version |
uuid3 | Validates that the field's value is a valid UUID v3 |
uuid3_rfc4122 | Validates that the field's value is a valid RFC4122 v3 UUID |
uuid4 | Validates that the field's value is a valid v4 UUID |
uuid4_rfc4122 | Validates that the field's value is a valid RFC4122 v4 UUID |
uuid5 | Validates that the field's value is a valid v5 UUID |
uuid5_rfc4122 | Validates that the field's value is a valid RFC4122 v5 UUID |
uuid_rfc4122 | Validates that the field's value is a valid RFC4122 UUID of any version |
md4 | Validates that the field's value is a valid MD4 |
md5 | Validates that the field's value is a valid MD5 |
sha256 | Validates that the field's value is a valid SHA256 |
sha384 | Validates that the field's value is a valid SHA384 |
sha512 | Validates that the field's value is a valid SHA512 |
ripemd128 | Validates that the field's value is a valid RIPEMD128 |
ripemd160 | Validates that the field's value is a valid RIPEMD160 |
tiger128 | Validates that the field's value is a valid TIGER128 |
tiger160 | Validates that the field's value is a valid TIGER160 |
tiger192 | Validates that the field's value is a valid TIGER192 |
semver | Validates that the current field's value is a valid semver version as defined in Semantic Versioning 2.0.0 |
ulid | Validates that the field's value is a valid ULID |
Comparison
| Tag | Description |
|---|---|
eq | Equals |
gt | Greater than |
gte | Greater than or equal |
lt | Less than |
lte | Less than or equal |
ne | Not equals |
Other
| Tag | Description |
|---|---|
dir | File directory |
file | File path |
isdefault | Validates that the current field's value is the default static value |
len | Field length |
max | Maximum value |
min | Minimum value |
oneof | Whether it is one of the enumerated values |
omitempty | If the field is not set, ignore the field |
required | Required value |
required_if | Field must be present and not empty only when all other specified fields equal the specified value |
required_unless | Field must be present and not empty unless all other specified fields equal the specified value |
required_with | Field must be present and not empty when any of the specified fields exist |
required_with_all | Field must be present and not empty when all specified fields exist |
required_without | Field must be present and not empty when any of the specified fields do not exist |
required_without_all | Field must be present and not empty when all specified fields do not exist |
excluded_if | Field can be absent or empty only when all other specified fields equal the specified value |
excluded_unless | Field can be absent or empty unless all other specified fields equal the specified value |
excluded_with | Field can be absent or empty when any of the specified fields exist |
excluded_with_all | Field can be absent or empty when all specified fields exist |
excluded_without | Field can be absent or empty when any of the specified fields do not exist |
excluded_without_all | Field can be absent or empty when all specified fields do not exist |
unique | Validates that each arr, map, slice value is unique |
Aliases
| Tag | Description |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Operators
| Tag | Description | Hex |
|---|---|---|
, | AND operator, use multiple validation tags, all conditions must be satisfied, no spaces between commas | 0x2c |
| | OR operator, use multiple validation tags, but only one needs to be satisfied | 0x7c |
- | Skip validation for this field | 0x2d |
= | Parameter matching symbol | 0x3d |
TIP
When matching operators during field validation, you need to replace them with utf8 hexadecimal representation, for example
filed string `validate:"contains=0x2c"`Usage
The following will introduce some basic usage of Validator with code examples.
Singleton
var validate *validator.ValidateWhen 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.
validate = validator.New()Struct Validation
func (v *Validate) Struct(s interface{}) errorThe 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
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' tagMap Validation
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Validates key-value pairs through a map tag.
Example
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.
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' tagValidate each user in a slice with struct validation
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' tagVariable Validation
Simple and easy to understand, no need for excessive explanation
Example 1
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' tagExample 2
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' tagTIP
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.
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:
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:
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
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' tagCustom 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:
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:
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
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:
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
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:
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 '' tagMulti-language
Translator component
go get github.com/go-playground/universal-translatorLocale component
go get github.com/go-playground/localesThe 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:
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
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.
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:
// 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
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:
{
"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:
{
"地址": "地址 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
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:
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
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)
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
[
{
"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.
