Skip to content

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)

Benchmarks: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (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

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

Importar

go
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

go
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

TagDescripción
eqcsfieldEn una estructura separada, valida que el valor del campo actual sea igual al campo especificado por el valor del parámetro
eqfieldValida que el valor del campo actual sea igual al campo especificado por el valor del parámetro
fieldcontainsValida que el valor del campo actual contenga el campo especificado por el valor del parámetro
fieldexcludesValida que el valor del campo actual no contenga el campo especificado por el valor del parámetro
gtcsfieldEn una estructura separada, valida que el valor del campo actual sea mayor que el campo especificado por el valor del parámetro
gtecsfieldEn una estructura separada, valida que el valor del campo actual sea mayor o igual que el campo especificado por el valor del parámetro
gtefieldValida que el valor del campo actual sea mayor o igual que el campo especificado por el valor del parámetro
gtfieldValida que el valor del campo actual sea mayor que el campo especificado por el valor del parámetro
ltcsfieldEn una estructura separada, valida que el valor del campo actual sea menor que el campo especificado por el valor del parámetro
ltecsfieldEn una estructura separada, valida que el valor del campo actual sea menor o igual que el campo especificado por el valor del parámetro
ltefieldValida que el valor del campo actual sea menor o igual que el campo especificado por el valor del parámetro
ltfieldValida que el valor del campo actual sea menor que el campo especificado por el valor del parámetro
necsfieldValida que el valor del campo actual no sea igual al campo en una estructura separada especificada por el valor del parámetro
nefieldValida que el valor del campo actual no sea igual al campo especificado por el valor del parámetro

Red

TagDescripción
cidrClassless Inter-Domain Routing CIDR
cidrv4Classless Inter-Domain Routing CIDRv4
cidrv6Classless Inter-Domain Routing CIDRv6
datauriData Uniform Resource Locator
fqdnFully Qualified Domain Name (FQDN)
hostnameHostname RFC 952
hostname_portValidación de campo de combinación <dns>:<port> comúnmente usada para direcciones de socket
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, también conocido como dirección de red local
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

Cadenas

TagDescripción
alphaValida que el valor del campo actual sea letras válidas
alphanumValida que el valor del campo actual sea alfanumérico válido
alphanumunicodeValida que el valor del campo actual sea valor unicode alfanumérico válido
alphaunicodeValida que el valor del campo actual sea valor unicode de letras válido
asciiValida que el valor del campo sea caracteres ASCII válidos
booleanValida que el valor del campo actual sea un valor booleano válido o pueda convertirse de forma segura a booleano
containsValida que el valor del campo contenga el texto especificado en el parámetro
containsanyValida que el valor del campo contenga cualquier carácter especificado en el parámetro
containsruneValida que el valor del campo contenga el rune especificado en el parámetro
endsnotwithValida que el valor del campo no termine con el texto especificado en el parámetro
endswithValida que el valor del campo termine con el texto especificado en el parámetro
excludesValida que el valor del campo no contenga el texto especificado en el parámetro
excludesallValida que el valor del campo no contenga ningún carácter especificado en el parámetro
excludesruneValida que el valor del campo no contenga el carácter especificado en el parámetro
lowercaseValida que el valor del campo actual sea una cadena en minúsculas
multibyteValida que el valor del campo tenga caracteres multibyte
numberValida que el valor del campo actual sea un número válido
numericValida que el valor del campo actual sea un valor numérico válido
printasciiValida que el valor del campo sea caracteres ASCII imprimibles válidos
startsnotwithValida que el valor del campo no comience con el texto especificado en el parámetro
startswithValida que el valor del campo comience con el texto especificado en el parámetro
uppercaseValida que el valor del campo actual sea una cadena en mayúsculas

Formatos

TagDescripción
base64Cadena Base64
base64urlCadena Base64URL
bicValida que el valor del campo actual sea un código BIC válido (código SWIFT) definido en ISO 9362
bcp47_language_tagValida que el valor del campo actual sea una etiqueta de idioma de la especificación BCP47
btc_addrValida que el valor del campo sea una dirección BTC válida
btc_addr_bech32Valida que el valor del campo sea una dirección bech32 BTC válida
credit_cardValida que el valor del campo actual sea un número de tarjeta de crédito válido
datetimeValida que el valor del campo actual sea una cadena de fecha y hora válida
e164Valida que el valor del campo actual sea un número de teléfono en formato e.164 válido
emailValida que el valor del campo actual sea una dirección de correo electrónico válida
eth_addrValida que el valor del campo sea una dirección Ethereum válida
hexadecimalValida que el valor del campo actual sea hexadecimal válido
hexcolorValida que el valor del campo actual sea un color hexadecimal válido
hslValida que el valor del campo actual sea un color HSL válido
hslaValida que el valor del campo actual sea un color HSLA válido
htmlValida que el valor del campo actual sea HTML válido
html_encodedValida que el valor del campo actual sea una codificación HTML válida
isbnValida que el valor del campo sea un ISBN válido v10 o v13 (Número Estándar Internacional de Libro)
isbn10Valida que el valor del campo sea un ISBN v10 válido (Número Estándar Internacional de Libro)
isbn13Valida que el valor del campo sea un ISBN v13 válido (Número Estándar Internacional de Libro)
iso3166_1_alpha2Valida que el valor del campo actual sea un código de país iso3166-1 alpha-2 válido
iso3166_1_alpha3Valida que el valor del campo actual sea un código de país iso3166-1 alpha-3 válido
iso3166_1_alpha_numericValida que el valor del campo actual sea un código de país alfanumérico iso3166-1 válido
iso3166_2Valida que el valor del campo actual sea un código de región de país válido (ISO 3166-2)
iso4217Valida que el valor del campo actual sea un código de moneda válido (ISO 4217)
jsonValida que el valor del campo actual sea una cadena json válida
jwtValida que el valor del campo actual sea una cadena JWT válida
latitudeValida que el valor del campo sea una coordenada de latitud válida
longitudeValida que el valor del campo sea una coordenada de longitud válida
postcode_iso3166_alpha2Valida según el valor del código de país en iso 3166 alpha 2
postcode_iso3166_alpha2_fieldValida mediante campo, este campo representa el valor del código de país en iso 3166 alpha 2
rgbValida que el valor del campo actual sea un color RGB válido
rgbaValida que el valor del campo actual sea un color RGBA válido
ssnValida que el valor del campo sea un SSN válido
timezoneValida que el valor del campo actual sea una cadena de zona horaria válida
uuidValida que el valor del campo sea un UUID válido en cualquier versión
uuid3Valida que el valor del campo sea un UUID v3 válido
uuid3_rfc4122Valida que el valor del campo sea un UUID v3 RFC4122 válido
uuid4Valida que el valor del campo sea un UUID v4 válido
uuid4_rfc4122Valida que el valor del campo sea un UUID v4 RFC4122 válido
uuid5Valida que el valor del campo sea un UUID v5 válido
uuid5_rfc4122Valida que el valor del campo sea un UUID v5 RFC4122 válido
uuid_rfc4122Valida que el valor del campo sea un UUID RFC4122 válido en cualquier versión
md4Valida que el valor del campo sea un MD4 válido
md5Valida que el valor del campo sea un MD5 válido
sha256Valida que el valor del campo sea un SHA256 válido
sha384Valida que el valor del campo sea un SHA384 válido
sha512Valida que el valor del campo sea un SHA512 válido
ripemd128Valida que el valor del campo sea un RIPEMD128 válido
ripemd160Valida que el valor del campo sea un RIPEMD160 válido
tiger128Valida que el valor del campo sea un TIGER128 válido
tiger160Valida que el valor del campo sea un TIGER160 válido
tiger192Valida que el valor del campo sea un TIGER192 válido
semverValida que el valor del campo actual sea una versión semver válida como se define en Semantic Versioning 2.0.0
ulidValida que el valor del campo sea un ULID válido

Comparación

TagDescripción
eqIgual a
gtMayor que
gteMayor o igual que
ltMenor que
lteMenor o igual que
neNo igual a

Otros

TagDescripción
dirDirectorio de archivos
fileRuta de archivo
isdefaultValida que el valor del campo actual sea el valor estático predeterminado
lenLongitud del campo
maxValor máximo
minValor mínimo
oneofSi es uno de los valores enumerados
omitemptySi el campo no está establecido, omite el campo
requiredValor obligatorio
required_ifEl campo debe existir y no estar vacío solo cuando todos los demás campos especificados son iguales al valor especificado
required_unlessEl campo debe existir y no estar vacío a menos que todos los demás campos especificados sean iguales al valor especificado
required_withEl campo debe existir y no estar vacío cuando cualquiera de los campos especificados existe
required_with_allEl campo debe existir y no estar vacío cuando todos los campos especificados existen
required_withoutEl campo debe existir y no estar vacío cuando cualquiera de los campos especificados no existe
required_without_allEl campo debe existir y no estar vacío cuando todos los campos especificados no existen
excluded_ifEl campo puede no existir o estar vacío solo cuando todos los demás campos especificados son iguales al valor especificado
excluded_unlessEl campo puede no existir o estar vacío a menos que todos los demás campos especificados sean iguales al valor especificado
excluded_withEl campo puede no existir o estar vacío cuando cualquiera de los campos especificados existe
excluded_with_allEl campo puede no existir o estar vacío cuando todos los campos especificados existen
excluded_withoutEl campo puede no existir o estar vacío cuando cualquiera de los campos especificados no existe
excluded_without_allEl campo puede no existir o estar vacío cuando todos los campos especificados no existen
uniqueValida que cada valor arr, map, slice sea único

Alias

TagDescripción
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

Operadores

TagDescripciónHex
,Operador AND, usa múltiples etiquetas de validación, todas las condiciones deben cumplirse, no puede haber espacios entre comas0x2c
|Operador OR, usa múltiples etiquetas de validación, pero solo necesita cumplirse una de ellas0x7c
-Omite la validación para este campo0x2d
=Símbolo de coincidencia de parámetro0x3d

TIP

Al validar campos, si se desea coincidir con operadores, se debe reemplazar con su forma hexadecimal utf8, por ejemplo

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

Uso

A continuación se presentará algunos usos básicos de Validator con algunos ejemplos de código.

Singleton

go
var validate *validator.Validate

Al 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.

go
validate = validator.New()

Validación de estructura

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

El 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

go
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' tag

Validación de map

go
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

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)
}

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

go
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' tag

Validación de estructura para cada usuario en el slice

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" 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' tag

Validación de variables

Es bastante simple y fácil de entender, no se dará más explicación

Ejemplo 1

go
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' tag

Ejemplo 2

go
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' tag

TIP

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.

go
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:

go
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:

go
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

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

Funció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:

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"
}

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

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

TIP

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:

go
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

go
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:

go
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 '' tag

Multilingüe

Componente traductor

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

Componente de región

go get github.com/go-playground/locales

El 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:

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"` // 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

go
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.

go
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:

go
// 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

go
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:

json
{
  "User.地址": "地址必须以文本'市'结尾",
  "User.姓名": "姓名必须包含文本'back'",
  "User.年龄": "年龄必须大于或等于 18",
  "User.性别": "性别为必填字段"
}

Al procesar la clave del map se obtiene el siguiente resultado:

json
{
  "地址": "地址必须以文本'市'结尾",
  "姓名": "姓名必须包含文本'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

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:"姓名"` // 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:

go
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

go
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)

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") // 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

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.

Golang editado por www.golangdev.cn