Validator Biblioteca de validación
Sitio oficial: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)
Documentación: validator/README.md at master · go-playground/validator (github.com)
Ejemplos oficiales: validator/_examples at master · go-playground/validator (github.com)
Introducción
go-playground/validator implementa un validador de valores basado en etiquetas de estructura, con las siguientes características únicas:
Se pueden usar etiquetas de validación y validadores personalizados para validación cruzada entre campos y estructuras
Slice, arrays, maps, o cualquier campo multidimensional pueden ser validados
Se puede profundizar para validar claves y valores de maps
Determina cómo manejar según su tipo básico antes de la validación
Puede manejar tipos de campos personalizados
Soporta etiquetas de alias, lo que permite mapear múltiples validaciones a una sola etiqueta para facilitar la definición de validaciones para estructuras
Puede extraer nombres de campos personalizados, como nombres JSON para mostrar en mensajes de error durante la validación
Mensajes de error multilingües personalizados
Componente de validación estándar predeterminado para el framework
gin
Instalación
go get github.com/go-playground/validator/v10Importar
import "github.com/go-playground/validator/v10"Etiquetas
El validador tiene muchas etiquetas de validación básicas, todas las funciones de validación correspondientes se pueden encontrar en el archivo baked_in.go, la etiqueta de estructura del validador es validate,
por ejemplo
type User {
age int `validate:"gte=18"` // indica mayor o igual a 18 años
}También se puede modificar la etiqueta predeterminada mediante el método setTagName.
Campos
| Tag | Descripción |
|---|---|
eqcsfield | En una estructura separada, valida que el valor del campo actual sea igual al campo especificado por el valor del parámetro |
eqfield | Valida que el valor del campo actual sea igual al campo especificado por el valor del parámetro |
fieldcontains | Valida que el valor del campo actual contenga el campo especificado por el valor del parámetro |
fieldexcludes | Valida que el valor del campo actual no contenga el campo especificado por el valor del parámetro |
gtcsfield | En una estructura separada, valida que el valor del campo actual sea mayor que el campo especificado por el valor del parámetro |
gtecsfield | En una estructura separada, valida que el valor del campo actual sea mayor o igual que el campo especificado por el valor del parámetro |
gtefield | Valida que el valor del campo actual sea mayor o igual que el campo especificado por el valor del parámetro |
gtfield | Valida que el valor del campo actual sea mayor que el campo especificado por el valor del parámetro |
ltcsfield | En una estructura separada, valida que el valor del campo actual sea menor que el campo especificado por el valor del parámetro |
ltecsfield | En una estructura separada, valida que el valor del campo actual sea menor o igual que el campo especificado por el valor del parámetro |
ltefield | Valida que el valor del campo actual sea menor o igual que el campo especificado por el valor del parámetro |
ltfield | Valida que el valor del campo actual sea menor que el campo especificado por el valor del parámetro |
necsfield | Valida que el valor del campo actual no sea igual al campo en una estructura separada especificada por el valor del parámetro |
nefield | Valida que el valor del campo actual no sea igual al campo especificado por el valor del parámetro |
Red
| Tag | Descripción |
|---|---|
cidr | Classless Inter-Domain Routing CIDR |
cidrv4 | Classless Inter-Domain Routing CIDRv4 |
cidrv6 | Classless Inter-Domain Routing CIDRv6 |
datauri | Data Uniform Resource Locator |
fqdn | Fully Qualified Domain Name (FQDN) |
hostname | Hostname RFC 952 |
hostname_port | Validación de campo de combinación <dns>:<port> comúnmente usada para direcciones de socket |
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, también conocido como dirección de red local |
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 |
Cadenas
| Tag | Descripción |
|---|---|
alpha | Valida que el valor del campo actual sea letras válidas |
alphanum | Valida que el valor del campo actual sea alfanumérico válido |
alphanumunicode | Valida que el valor del campo actual sea valor unicode alfanumérico válido |
alphaunicode | Valida que el valor del campo actual sea valor unicode de letras válido |
ascii | Valida que el valor del campo sea caracteres ASCII válidos |
boolean | Valida que el valor del campo actual sea un valor booleano válido o pueda convertirse de forma segura a booleano |
contains | Valida que el valor del campo contenga el texto especificado en el parámetro |
containsany | Valida que el valor del campo contenga cualquier carácter especificado en el parámetro |
containsrune | Valida que el valor del campo contenga el rune especificado en el parámetro |
endsnotwith | Valida que el valor del campo no termine con el texto especificado en el parámetro |
endswith | Valida que el valor del campo termine con el texto especificado en el parámetro |
excludes | Valida que el valor del campo no contenga el texto especificado en el parámetro |
excludesall | Valida que el valor del campo no contenga ningún carácter especificado en el parámetro |
excludesrune | Valida que el valor del campo no contenga el carácter especificado en el parámetro |
lowercase | Valida que el valor del campo actual sea una cadena en minúsculas |
multibyte | Valida que el valor del campo tenga caracteres multibyte |
number | Valida que el valor del campo actual sea un número válido |
numeric | Valida que el valor del campo actual sea un valor numérico válido |
printascii | Valida que el valor del campo sea caracteres ASCII imprimibles válidos |
startsnotwith | Valida que el valor del campo no comience con el texto especificado en el parámetro |
startswith | Valida que el valor del campo comience con el texto especificado en el parámetro |
uppercase | Valida que el valor del campo actual sea una cadena en mayúsculas |
Formatos
| Tag | Descripción |
|---|---|
base64 | Cadena Base64 |
base64url | Cadena Base64URL |
bic | Valida que el valor del campo actual sea un código BIC válido (código SWIFT) definido en ISO 9362 |
bcp47_language_tag | Valida que el valor del campo actual sea una etiqueta de idioma de la especificación BCP47 |
btc_addr | Valida que el valor del campo sea una dirección BTC válida |
btc_addr_bech32 | Valida que el valor del campo sea una dirección bech32 BTC válida |
credit_card | Valida que el valor del campo actual sea un número de tarjeta de crédito válido |
datetime | Valida que el valor del campo actual sea una cadena de fecha y hora válida |
e164 | Valida que el valor del campo actual sea un número de teléfono en formato e.164 válido |
email | Valida que el valor del campo actual sea una dirección de correo electrónico válida |
eth_addr | Valida que el valor del campo sea una dirección Ethereum válida |
hexadecimal | Valida que el valor del campo actual sea hexadecimal válido |
hexcolor | Valida que el valor del campo actual sea un color hexadecimal válido |
hsl | Valida que el valor del campo actual sea un color HSL válido |
hsla | Valida que el valor del campo actual sea un color HSLA válido |
html | Valida que el valor del campo actual sea HTML válido |
html_encoded | Valida que el valor del campo actual sea una codificación HTML válida |
isbn | Valida que el valor del campo sea un ISBN válido v10 o v13 (Número Estándar Internacional de Libro) |
isbn10 | Valida que el valor del campo sea un ISBN v10 válido (Número Estándar Internacional de Libro) |
isbn13 | Valida que el valor del campo sea un ISBN v13 válido (Número Estándar Internacional de Libro) |
iso3166_1_alpha2 | Valida que el valor del campo actual sea un código de país iso3166-1 alpha-2 válido |
iso3166_1_alpha3 | Valida que el valor del campo actual sea un código de país iso3166-1 alpha-3 válido |
iso3166_1_alpha_numeric | Valida que el valor del campo actual sea un código de país alfanumérico iso3166-1 válido |
iso3166_2 | Valida que el valor del campo actual sea un código de región de país válido (ISO 3166-2) |
iso4217 | Valida que el valor del campo actual sea un código de moneda válido (ISO 4217) |
json | Valida que el valor del campo actual sea una cadena json válida |
jwt | Valida que el valor del campo actual sea una cadena JWT válida |
latitude | Valida que el valor del campo sea una coordenada de latitud válida |
longitude | Valida que el valor del campo sea una coordenada de longitud válida |
postcode_iso3166_alpha2 | Valida según el valor del código de país en iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Valida mediante campo, este campo representa el valor del código de país en iso 3166 alpha 2 |
rgb | Valida que el valor del campo actual sea un color RGB válido |
rgba | Valida que el valor del campo actual sea un color RGBA válido |
ssn | Valida que el valor del campo sea un SSN válido |
timezone | Valida que el valor del campo actual sea una cadena de zona horaria válida |
uuid | Valida que el valor del campo sea un UUID válido en cualquier versión |
uuid3 | Valida que el valor del campo sea un UUID v3 válido |
uuid3_rfc4122 | Valida que el valor del campo sea un UUID v3 RFC4122 válido |
uuid4 | Valida que el valor del campo sea un UUID v4 válido |
uuid4_rfc4122 | Valida que el valor del campo sea un UUID v4 RFC4122 válido |
uuid5 | Valida que el valor del campo sea un UUID v5 válido |
uuid5_rfc4122 | Valida que el valor del campo sea un UUID v5 RFC4122 válido |
uuid_rfc4122 | Valida que el valor del campo sea un UUID RFC4122 válido en cualquier versión |
md4 | Valida que el valor del campo sea un MD4 válido |
md5 | Valida que el valor del campo sea un MD5 válido |
sha256 | Valida que el valor del campo sea un SHA256 válido |
sha384 | Valida que el valor del campo sea un SHA384 válido |
sha512 | Valida que el valor del campo sea un SHA512 válido |
ripemd128 | Valida que el valor del campo sea un RIPEMD128 válido |
ripemd160 | Valida que el valor del campo sea un RIPEMD160 válido |
tiger128 | Valida que el valor del campo sea un TIGER128 válido |
tiger160 | Valida que el valor del campo sea un TIGER160 válido |
tiger192 | Valida que el valor del campo sea un TIGER192 válido |
semver | Valida que el valor del campo actual sea una versión semver válida como se define en Semantic Versioning 2.0.0 |
ulid | Valida que el valor del campo sea un ULID válido |
Comparación
| Tag | Descripción |
|---|---|
eq | Igual a |
gt | Mayor que |
gte | Mayor o igual que |
lt | Menor que |
lte | Menor o igual que |
ne | No igual a |
Otros
| Tag | Descripción |
|---|---|
dir | Directorio de archivos |
file | Ruta de archivo |
isdefault | Valida que el valor del campo actual sea el valor estático predeterminado |
len | Longitud del campo |
max | Valor máximo |
min | Valor mínimo |
oneof | Si es uno de los valores enumerados |
omitempty | Si el campo no está establecido, omite el campo |
required | Valor obligatorio |
required_if | El campo debe existir y no estar vacío solo cuando todos los demás campos especificados son iguales al valor especificado |
required_unless | El campo debe existir y no estar vacío a menos que todos los demás campos especificados sean iguales al valor especificado |
required_with | El campo debe existir y no estar vacío cuando cualquiera de los campos especificados existe |
required_with_all | El campo debe existir y no estar vacío cuando todos los campos especificados existen |
required_without | El campo debe existir y no estar vacío cuando cualquiera de los campos especificados no existe |
required_without_all | El campo debe existir y no estar vacío cuando todos los campos especificados no existen |
excluded_if | El campo puede no existir o estar vacío solo cuando todos los demás campos especificados son iguales al valor especificado |
excluded_unless | El campo puede no existir o estar vacío a menos que todos los demás campos especificados sean iguales al valor especificado |
excluded_with | El campo puede no existir o estar vacío cuando cualquiera de los campos especificados existe |
excluded_with_all | El campo puede no existir o estar vacío cuando todos los campos especificados existen |
excluded_without | El campo puede no existir o estar vacío cuando cualquiera de los campos especificados no existe |
excluded_without_all | El campo puede no existir o estar vacío cuando todos los campos especificados no existen |
unique | Valida que cada valor arr, map, slice sea único |
Alias
| Tag | Descripción |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Operadores
| Tag | Descripción | Hex |
|---|---|---|
, | Operador AND, usa múltiples etiquetas de validación, todas las condiciones deben cumplirse, no puede haber espacios entre comas | 0x2c |
| | Operador OR, usa múltiples etiquetas de validación, pero solo necesita cumplirse una de ellas | 0x7c |
- | Omite la validación para este campo | 0x2d |
= | Símbolo de coincidencia de parámetro | 0x3d |
TIP
Al validar campos, si se desea coincidir con operadores, se debe reemplazar con su forma hexadecimal utf8, por ejemplo
filed string `validate:"contains=0x2c"`Uso
A continuación se presentará algunos usos básicos de Validator con algunos ejemplos de código.
Singleton
var validate *validator.ValidateAl usar, los oficiales recomiendan que durante todo el ciclo de vida del programa, solo debe haber una instancia del validador, esto ayudará a almacenar algunos datos en caché.
Crear validador
Al usar Validator solo sin integrar otros frameworks, necesitamos crear manualmente el validador.
validate = validator.New()Validación de estructura
func (v *Validate) Struct(s interface{}) errorEl método Struct se usa para validar todos los campos públicos de una estructura, por defecto realiza automáticamente la validación de estructuras anidadas, cuando se pasa un valor inválido o el valor es nil, devolverá InvalidValidationError, si la validación falla devolverá ValidationErrors.
Ejemplo
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // nombre contiene jack
Age int `validate:"gte=18"` // mayor o igual a 18 años
Address string `validate:"endswith=市"` // termina con 市
}
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)
}Salida
User.Age
Age
User.Age
Age
gte
gte
int
int
17
18
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tagValidación de map
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Validación de pares clave-valor mediante una etiqueta map.
Ejemplo
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)
}Salida
map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]Validación de slice
Validación de slice de cadenas, antes del dive la tag valida el slice, después del dive la tag valida los valores del slice, el slice anidado también funciona igual, usa tantos dive como dimensiones tenga
func TestSlice1(t *testing.T) {
list := []string{"jack", "mike", "lisa", "golang"}
err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // longitud máxima del slice es 5, los elementos deben contener el carácter a, y la longitud mínima es 5
fmt.Println(err)
}Salida
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' tagValidación de estructura para cada usuario en el slice
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" significa validación profunda, cuando el elemento es una estructura, automáticamente realiza la validación de estructura
fmt.Println(err)
}Salida
Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tagValidación de variables
Es bastante simple y fácil de entender, no se dará más explicación
Ejemplo 1
func TestVar(t *testing.T) {
name := "jack"
err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // longitud máxima 5, longitud mínima 1, contiene la letra a, termina con la letra l
fmt.Println(err)
}Salida
Key: '' Error:Field validation for '' failed on the 'endswith' tagEjemplo 2
func TestVar1(t *testing.T) {
age := 18
err := validator.New().Var(age, "gte=19")
fmt.Println(err)
}Salida
Key: '' Error:Field validation for '' failed on the 'gte' tagTIP
El método Var puede validar tipos que incluyen estructuras, variables, slices, maps, debe usarse razonablemente combinado con la etiqueta dive.
Validación de campos
Los parámetros de validación de campos ya no son tipos básicos, sino nombres de campos de estructura, pueden ser sus propios campos o campos de estructuras anidadas.
type Password struct {
FirstPassword string `validate:"eqfield=SecondPassword"` // valida si las dos contraseñas ingresadas son iguales
SecondPassword string
}
type RegisterUser struct {
Username string `validate:"necsfield=Password.FirstPassword"` // por seguridad en el registro, se prohíbe que la contraseña sea igual al nombre de usuario
Password Password
}
func TestCrossStructFieldValidate(t *testing.T) {
validate = validator.New()
// falla
fmt.Println(validate.Struct(RegisterUser{
Username: "gopher",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gophers",
},
}))
// éxito
fmt.Println(validate.Struct(RegisterUser{
Username: "gophers",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gopher",
},
}))
}Salida
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
Al usar validación de campos, cuando el campo o estructura pasado como parámetro en la Tag no existe, se juzgará directamente como validación fallida, por ejemplo:
type Password struct {
FirstPassword string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
SecondPassword string
}Para este tipo de errores ortográficos, es difícil de detectar, y durante la validación solo se mostrará como no aprobada, se debe prestar mucha atención.
Avanzado
A continuación se explicarán algunas técnicas de uso avanzado y más operaciones personalizadas.
Alias personalizado
A veces, para un campo hay muchas etiquetas de validación, cuando quieres reutilizarlo en otro campo, podrías copiar y pegar directamente, pero esta no es la mejor solución, un mejor método es registrar un alias para mejorar la reutilización, vea el siguiente ejemplo:
var validate *validator.Validate
const PERSON_NAME_RULES = "max=10,min=1,contains=jack"
func TestAlias(t *testing.T) {
validate = validator.New()
// registrar alias
validate.RegisterAlias("namerules", PERSON_NAME_RULES)
type person struct {
FirstName string `validate:"namerules"` // usar alias
LastName string `validate:"namerules"`
}
err := validate.Struct(person{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}Salida
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' tagFunción de validación personalizada
Aunque las etiquetas de validación incluidas en el componente son suficientes para la mayoría de los casos, a veces para necesidades especiales es necesario definir lógica personalizada, Validator nos proporciona API relacionadas para personalizar funciones de validación. A continuación vea un ejemplo:
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"
}Se creó una función para determinar si el valor del campo es igual a "666", y su Tag correspondiente es is666, la salida es
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
Si la Tag registrada ya existe, será sobrescrita por la existente, es decir, se puede "reescribir" la lógica de validación predeterminada de la Tag.
Función de validación de tipo personalizado
La función de validación de tipo es específica para un tipo determinado, generalmente se usa para tipos no básicos, también se puede sobrescribir la validación predeterminada de tipos básicos, vea un ejemplo a continuación:
type Address struct {
name string
}
func TestCustomTypeValidate(t *testing.T) {
validate = validator.New()
validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // registrar función de validación de tipo y el tipo correspondiente
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 {
// manejo de errores
if address.name == "" {
return address.name
}
return value // devolver el campo representa que la validación es correcta
}
return nil
}Salida
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>TIP
Registrar múltiples tipos en una función también funciona de la misma manera
Función de validación de estructura personalizada
La diferencia de la función de validación de estructura es que los parámetros de otras funciones son campos, mientras que el parámetro de esta función es la estructura, vea el siguiente ejemplo:
type People struct {
FirstName string
LastName string
}
func TestCustomStructLevel(t *testing.T) {
validate = validator.New()
validate.RegisterStructValidation(PeopleValidate, People{}) // igual que el registro de tipo, se pueden pasar múltiples tipos de estructuras
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", "", "")
}
}Salida
Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tagMultilingüe
Componente traductor
go get github.com/go-playground/universal-translatorComponente de región
go get github.com/go-playground/localesEl idioma predeterminado del validador es inglés, y al desarrollar proyectos de software, es posible que necesitemos usar más de un idioma, en este caso necesitamos usar el componente de internacionalización multilingüe, vea un ejemplo a continuación:
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"` // nombre contiene jack
Age int `validate:"gte=18"` // mayor o igual a 18 años
Address string `validate:"endswith=市"` // termina con 市
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func TestTranslate(t *testing.T) {
zh := zh.New()
// el primero es de respaldo, los siguientes son idiomas soportados, puede haber múltiples
uni = ut.New(zh, zh)
// el idioma aquí generalmente se puede obtener del encabezado Accept-Language de la solicitud http
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // registrar traductor predeterminado
}
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}Salida
map[User.Address:Address debe terminar con el texto '市' User.Age:Age debe ser mayor o igual a 18 User.Name:Name debe contener el texto 'jack']También se puede traducir cada error individualmente
for _, fieldError := range err.(validator.ValidationErrors) {
fmt.Println(fieldError.Translate(trans))
}Salida
Name debe contener el texto 'jack'
Age debe ser mayor o igual a 18
Address debe terminar con el texto '市'Se puede ver que el valor de retorno es un map, se puede ver que la traducción básica de mensajes de error ya está lograda, pero aún no es suficiente para usar, necesitamos continuar embelleciendo la información de error para facilitar la conexión con clientes o frontend.
type User struct {
Name string `validate:"contains=jack" label:"姓名"` // nombre contiene jack
Age int `validate:"gte=18" label:"年龄"` // mayor o igual a 18 años
Address string `validate:"endswith=市" label:"地址"` // termina con 市
Sex string `validate:"required" label:"性别"`
}Primero personalice la Tag label, su valor es el nombre en chino del campo, luego registre un TagNameFunc a través del validador, su función es obtener el nombre del campo o reemplazar el nombre original. En el archivo errors.go, el método Field() string dice en el comentario: "El nombre del campo con etiqueta tiene prioridad sobre el nombre real del campo", por lo que cuando ocurra un error, se puede usar el nombre en chino personalizado para reemplazar la palabra en inglés. TagNameFunc es el siguiente:
// agregamos una etiqueta personalizada, esta etiqueta se usa para dar un nombre en chino al campo de la estructura, reemplazará el nombre original del campo
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}Finalmente registrar
validate.RegisterTagNameFunc(CustomTagNameFunc)Ejecutar nuevamente la salida
姓名 debe contener el texto 'jack'
年龄 debe ser mayor o igual a 18
地址 debe terminar con el texto '市'Pero esto aún no es suficiente, todavía no es suficiente como información de error para devolver al frontend, necesitamos formatear la información a json o cualquier formato adecuado para la transmisión de mensajes, podrías pensar en serializar el map directamente a json, esta es una solución, pero podrías obtener el siguiente resultado:
{
"User.地址": "地址必须以文本'市'结尾",
"User.姓名": "姓名必须包含文本'back'",
"User.年龄": "年龄必须大于或等于 18",
"User.性别": "性别为必填字段"
}Al procesar la clave del map se obtiene el siguiente resultado:
{
"地址": "地址必须以文本'市'结尾",
"姓名": "姓名必须包含文本'back'",
"年龄": "年龄必须大于或等于 18",
"性别": "性别为必填字段"
}Sin embargo, no se recomienda devolver este tipo de información al frontend, podemos procesarlo como una cadena para devolver como información
姓名必须包含文本'back', 年龄必须大于或等于 18, 地址必须以文本'市'结尾,性别为必填字段,Código completo
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:"姓名"` // nombre contiene jack
Age int `validate:"gte=18" label:"年龄"` // mayor o igual a 18 años
Address string `validate:"endswith=市" label:"地址"` // termina con 市
Sex string `validate:"required" label:"性别"`
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
// agregamos una etiqueta personalizada, esta etiqueta se usa para dar un nombre en chino al campo de la estructura, reemplazará el nombre original del campo
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)
// el idioma aquí generalmente se puede obtener del encabezado Accept-Language de la solicitud http
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // registrar traductor predeterminado
}
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()
}Finalmente, si sientes que los mensajes de error son demasiado fríos y deseas que sean más humanizados, puedes sobrescribir los mensajes de error de etiquetas específicas, esto requiere usar el método RegisterTranslation, y también necesitas usar dos tipos de funciones, una es RegisterTranslationsFunc responsable de registrar la plantilla de traducción de la Tag correspondiente, la otra es TranslationFunc, responsable de procesar la plantilla para obtener el contenido de traducción final. Aquí usamos required como ejemplo:
func requiredOverrideRegister(ut ut.Translator) error { // esta función sirve para registrar la plantilla de traducción
return ut.Add("required", "{} es un campo que debe completarse", true) // {} es un marcador de posición, true representa si se sobrescribe una plantilla existente
}
func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // esta función sirve para traducir el contenido
t, _ := ut.T("required", fe.Field()) // puede haber múltiples parámetros, depende de cuántos marcadores de posición tenga la plantilla de la Tag registrada correspondiente
return t
}Finalmente registrar
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)Resultado
姓名必须包含文本'back', 年龄必须大于或等于 18, 地址必须以文本'市'结尾,性别是一个必须填写的字段,Archivos de idioma
De hecho, registrar código uno por uno es muy tedioso, universal-translator proporciona la capacidad de traducir mediante archivos de configuración JSON: 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") // se recomienda importar después de registrar, de esta manera se puede sobrescribir la Tag original
if er != nil {
log.Fatal(er)
}
type Gopher struct {
Language string `validate:"required"`
}
err := validate.Struct(Gopher{
"",
})
fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}Archivo JSON
[
{
"locale": "zh",
"key": "required",
"trans": "Este es un campo muy importante {0}, debes completarlo",
"override": true
}
]Salida
map[Gopher.Language:Este es un campo muy importante Language,debes completarlo]TIP
universal-translator tiene muchas trampas al usar, si quieres sobrescribir la Tag original, type y rule pueden dejarse sin llenar, porque la configuración original tampoco los tiene, es mejor mantener la consistencia. Si se llena algún type, la configuración se agregará al map correspondiente, si es Cardinal u otro type y rule está configurado con one u otros, entonces se necesita la configuración local correspondiente para usarlo normalmente, de lo contrario se producirá un error.
