Skip to content

Validator Validation Library

ที่อยู่ทางการ: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)

ที่อยู่เอกสาร: validator/README.md at master · go-playground/validator (github.com)

ตัวอย่างทางการ: validator/_examples at master · go-playground/validator (github.com)

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

บทนำ

go-playground/validator ดำเนินการตัวตรวจสอบค่าตามแท็กโครงสร้าง体 มีคุณสมบัติที่ไม่เหมือนใครดังนี้:

  • สามารถใช้แท็กตรวจสอบและตัวตรวจสอบที่กำหนดเองสำหรับการตรวจสอบข้ามฟิลด์และข้ามโครงสร้าง体

  • สไลซ์, อาร์เรย์, map หรือฟิลด์หลายมิติใดๆ สามารถตรวจสอบได้

  • สามารถตรวจสอบ key และ value ของ map ได้

  • กำหนดวิธีการจัดการตามประเภทพื้นฐานก่อนการตรวจสอบ

  • สามารถจัดการฟิลด์ประเภทที่กำหนดเองได้

  • รองรับแท็กนามธรรม ซึ่งอนุญาตให้แมปการตรวจสอบหลายรายการกับแท็กเดียว เพื่อให้ง่ายต่อการกำหนดการตรวจสอบสำหรับโครงสร้าง体

  • สามารถแยกชื่อฟิลด์ที่กำหนดเอง เช่น สามารถแยกชื่อ JSON ในการตรวจสอบเพื่อแสดงในข้อความข้อผิดพลาด

  • ข้อผิดพลาดหลายภาษาที่กำหนดเอง

  • คอมโพเนนต์ตรวจสอบเริ่มต้นมาตรฐานของเฟรมเวิร์ก gin

การติดตั้ง

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

การนำเข้า

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

แท็ก

ตัวตรวจสอบมีแท็กตรวจสอบพื้นฐานมากมาย ฟังก์ชันตรวจสอบที่สอดคล้องกับแท็กทั้งหมดสามารถพบได้ในไฟล์ baked_in.go แท็กโครงสร้าง体ของตัวตรวจสอบคือ validate

เช่น

go
type User {
  age int `validate:"gte=18"` //表示大于等于18岁
}

ยังสามารถแก้ไขแท็กเริ่มต้นผ่านเมธอด setTagName

ฟิลด์

Tagคำอธิบาย
eqcsfieldในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
eqfieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
fieldcontainsตรวจสอบว่าค่าของฟิลด์ปัจจุบันมีฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
fieldexcludesตรวจสอบว่าค่าของฟิลด์ปัจจุบันไม่มีฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
gtcsfieldในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
gtecsfieldในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
gtefieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
gtfieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันมากกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
ltcsfieldในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
ltecsfieldในโครงสร้างที่แยกต่างหาก ตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
ltefieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าหรือเท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
ltfieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันน้อยกว่าฟิลด์ที่ระบุโดยค่าของพารามิเตอร์
necsfieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันไม่เท่ากับฟิลด์ในโครงสร้างที่แยกต่างหากที่ระบุโดยค่าของพารามิเตอร์
nefieldตรวจสอบว่าค่าของฟิลด์ปัจจุบันไม่เท่ากับฟิลด์ที่ระบุโดยค่าของพารามิเตอร์

Network

Tagคำอธิบาย
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_portการตรวจสอบฟิลด์สำหรับชุดค่าผสม<dns>:<port> ที่มักใช้สำหรับที่อยู่ซ็อกเก็ต
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 หรือที่เรียกว่าที่อยู่ LAN
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

String

Tagคำอธิบาย
alphaตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นตัวอักษรที่ถูกต้อง
alphanumตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นตัวอักษรตัวเลขที่ถูกต้อง
alphanumunicodeตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าตัวอักษรตัวเลข unicode ที่ถูกต้อง
alphaunicodeตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าตัวอักษร unicode ที่ถูกต้อง
asciiตรวจสอบว่าค่าของฟิลด์เป็นอักขระ ASCII ที่ถูกต้อง
booleanตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าบูลีนที่ถูกต้องหรือสามารถแปลงเป็นบูลีนได้อย่างปลอดภัย
containsตรวจสอบว่าค่าของฟิลด์มีข้อความที่ระบุในพารามิเตอร์
containsanyตรวจสอบว่าค่าของฟิลด์มีอักขระใดๆ ที่ระบุในพารามิเตอร์
containsruneตรวจสอบว่าค่าของฟิลด์มี rune ที่ระบุในพารามิเตอร์
endsnotwithตรวจสอบว่าค่าของฟิลด์ไม่ลงท้ายด้วยข้อความที่ระบุในพารามิเตอร์
endswithตรวจสอบว่าค่าของฟิลด์ลงท้ายด้วยข้อความที่ระบุในพารามิเตอร์
excludesตรวจสอบว่าค่าของฟิลด์ไม่มีข้อความที่ระบุในพารามิเตอร์
excludesallตรวจสอบว่าค่าของฟิลด์ไม่มีอักขระใดๆ ที่ระบุในพารามิเตอร์
excludesruneตรวจสอบว่าค่าของฟิลด์ไม่มีอักขระที่ระบุในพารามิเตอร์
lowercaseตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงตัวพิมพ์เล็ก
multibyteตรวจสอบว่าค่าของฟิลด์มีอักขระหลายไบต์
numberตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นตัวเลขที่ถูกต้อง
numericตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าตัวเลขที่ถูกต้อง
printasciiตรวจสอบว่าค่าของฟิลด์เป็นอักขระ ASCII ที่พิมพ์ได้
startsnotwithตรวจสอบว่าค่าของฟิลด์ไม่เริ่มต้นด้วยข้อความที่ระบุในพารามิเตอร์
startswithตรวจสอบว่าค่าของฟิลด์เริ่มต้นด้วยข้อความที่ระบุในพารามิเตอร์
uppercaseตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงตัวพิมพ์ใหญ่

Format

Tagคำอธิบาย
base64สตริง Base64
base64urlสตริง Base64URL
bicตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัส BIC ที่ถูกต้องตาม ISO 9362 (รหัส SWIFT)
bcp47_language_tagตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นแท็กภาษาตามข้อกำหนด BCP47
btc_addrตรวจสอบว่าค่าของฟิลด์เป็นที่อยู่ BTC ที่ถูกต้อง
btc_addr_bech32ตรวจสอบว่าค่าของฟิลด์เป็นที่อยู่ bech32 BTC ที่ถูกต้อง
credit_cardตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นหมายเลขบัตรเครดิตที่ถูกต้อง
datetimeตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงวันที่เวลาที่ถูกต้อง
e164ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นหมายเลขโทรศัพท์รูปแบบ e.164 ที่ถูกต้อง
emailตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นที่อยู่อีเมลที่ถูกต้อง
eth_addrตรวจสอบว่าค่าของฟิลด์เป็นที่อยู่ Ethereum ที่ถูกต้อง
hexadecimalตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นเลขฐานสิบหกที่ถูกต้อง
hexcolorตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสีฐานสิบหกที่ถูกต้อง
hslตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสี HSL ที่ถูกต้อง
hslaตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสี HSLA ที่ถูกต้อง
htmlตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็น HTML ที่ถูกต้อง
html_encodedตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็น HTML encoding ที่ถูกต้อง
isbnตรวจสอบว่าค่าของฟิลด์เป็น ISBN v10 หรือ v13 ที่ถูกต้อง (หมายเลขมาตรฐานสากลของหนังสือ)
isbn10ตรวจสอบว่าค่าของฟิลด์เป็น ISBN v10 ที่ถูกต้อง (หมายเลขมาตรฐานสากลของหนังสือ)
isbn13ตรวจสอบว่าค่าของฟิลด์เป็น ISBN v13 ที่ถูกต้อง (หมายเลขมาตรฐานสากลของหนังสือ)
iso3166_1_alpha2ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัสประเทศ iso3166-1 alpha-2 ที่ถูกต้อง
iso3166_1_alpha3ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัสประเทศ iso3166-1 alpha-3 ที่ถูกต้อง
iso3166_1_alpha_numericตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัสประเทศตัวเลข iso3166-1 ที่ถูกต้อง
iso3166_2ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัสภูมิภาคประเทศ (ISO 3166-2)
iso4217ตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นรหัสสกุลเงิน (ISO 4217)
jsonตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริง json ที่ถูกต้อง
jwtตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริง JWT ที่ถูกต้อง
latitudeตรวจสอบว่าค่าของฟิลด์เป็นพิกัดละติจูดที่ถูกต้อง
longitudeตรวจสอบว่าค่าของฟิลด์เป็นพิกัดลองจิจูดที่ถูกต้อง
postcode_iso3166_alpha2ตรวจสอบตามค่ารหัสประเทศใน iso 3166 alpha 2
postcode_iso3166_alpha2_fieldตรวจสอบผ่านฟิลด์ ฟิลด์นี้แสดงค่ารหัสประเทศใน iso 3166 alpha 2
rgbตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสี RGB ที่ถูกต้อง
rgbaตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสี RGBA ที่ถูกต้อง
ssnตรวจสอบว่าค่าของฟิลด์เป็น SSN ที่ถูกต้อง
timezoneตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นสตริงเขตเวลาที่ถูกต้อง
uuidตรวจสอบว่าค่าของฟิลด์เป็น UUID ที่ถูกต้อง在任何版本
uuid3ตรวจสอบว่าค่าของฟิลด์เป็น UUID v3 ที่ถูกต้อง
uuid3_rfc4122ตรวจสอบว่าค่าของฟิลด์เป็น RFC4122 v3 UUID ที่ถูกต้อง
uuid4ตรวจสอบว่าค่าของฟิลด์เป็น v4 UUID ที่ถูกต้อง
uuid4_rfc4122ตรวจสอบว่าค่าของฟิลด์เป็น RFC4122 v4 UUID ที่ถูกต้อง
uuid5ตรวจสอบว่าค่าของฟิลด์เป็น v5 UUID ที่ถูกต้อง
uuid5_rfc4122ตรวจสอบว่าค่าของฟิลด์เป็น RFC4122 v5 UUID ที่ถูกต้อง
uuid_rfc4122ตรวจสอบว่าค่าของฟิลด์เป็น RFC4122 UUID ที่ถูกต้อง在任何版本
md4ตรวจสอบว่าค่าของฟิลด์เป็น MD4 ที่ถูกต้อง
md5ตรวจสอบว่าค่าของฟิลด์เป็น MD5 ที่ถูกต้อง
sha256ตรวจสอบว่าค่าของฟิลด์เป็น SHA256 ที่ถูกต้อง
sha384ตรวจสอบว่าค่าของฟิลด์เป็น SHA384 ที่ถูกต้อง
sha512ตรวจสอบว่าค่าของฟิลด์เป็น SHA512 ที่ถูกต้อง
ripemd128ตรวจสอบว่าค่าของฟิลด์เป็น RIPEMD128 ที่ถูกต้อง
ripemd160ตรวจสอบว่าค่าของฟิลด์เป็น RIPEMD160 ที่ถูกต้อง
tiger128ตรวจสอบว่าค่าของฟิลด์เป็น TIGER128 ที่ถูกต้อง
tiger160ตรวจสอบว่าค่าของฟิลด์เป็น TIGER160 ที่ถูกต้อง
tiger192ตรวจสอบว่าค่าของฟิลด์เป็น TIGER192 ที่ถูกต้อง
semverตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นเวอร์ชัน semver ที่ถูกต้องตาม Semantic Versioning 2.0.0
ulidตรวจสอบว่าค่าของฟิลด์เป็น ULID ที่ถูกต้อง

Comparison

Tagคำอธิบาย
eqเท่ากับ
gtมากกว่า
gteมากกว่าหรือเท่ากับ
ltน้อยกว่า
lteน้อยกว่าหรือเท่ากับ
neไม่เท่ากับ

Other

Tagคำอธิบาย
dirไดเรกทอรีไฟล์
fileพาธไฟล์
isdefaultตรวจสอบว่าค่าของฟิลด์ปัจจุบันเป็นค่าคงที่เริ่มต้น
lenความยาวฟิลด์
maxค่าสูงสุด
minค่าต่ำสุด
oneofเป็นหนึ่งในค่าที่ระบุ
omitemptyหากฟิลด์ไม่ได้ตั้งค่า ให้ละเว้นฟิลด์
requiredต้องมีค่า
required_ifเฉพาะเมื่อฟิลด์อื่นที่ระบุทั้งหมดเท่ากับค่าที่ระบุ ฟิลด์ที่ตรวจสอบต้องมีอยู่และไม่为空
required_unlessเว้นแต่ฟิลด์อื่นที่ระบุทั้งหมดเท่ากับค่าที่ระบุ ฟิลด์ที่ตรวจสอบต้องมีอยู่และไม่为空
required_withเมื่อฟิลด์ที่ระบุใดฟิลด์หนึ่งมีอยู่ ฟิลด์ที่ตรวจสอบต้องมีอยู่และไม่为空
required_with_allเมื่อฟิลด์ที่ระบุทั้งหมดมีอยู่ ฟิลด์ที่ตรวจสอบต้องมีอยู่และไม่为空
required_withoutเมื่อฟิลด์ที่ระบุใดฟิลด์หนึ่งไม่มีอยู่ ฟิลด์ที่ตรวจสอบต้องมีอยู่และไม่为空
required_without_allเมื่อฟิลด์ที่ระบุทั้งหมดไม่มีอยู่ ฟิลด์ที่ตรวจสอบต้องมีอยู่และไม่为空
excluded_ifเฉพาะเมื่อฟิลด์อื่นที่ระบุทั้งหมดเท่ากับค่าที่ระบุ ฟิลด์ที่ตรวจสอบอาจไม่มีอยู่หรือ为空
excluded_unlessเว้นแต่ฟิลด์อื่นที่ระบุทั้งหมดเท่ากับค่าที่ระบุ ฟิลด์ที่ตรวจสอบอาจไม่มีอยู่หรือ为空
excluded_withเมื่อฟิลด์ที่ระบุใดฟิลด์หนึ่งมีอยู่ ฟิลด์ที่ตรวจสอบอาจไม่มีอยู่หรือ为空
excluded_with_allเมื่อฟิลด์ที่ระบุทั้งหมดมีอยู่ ฟิลด์ที่ตรวจสอบอาจไม่มีอยู่หรือ为空
excluded_withoutเมื่อฟิลด์ที่ระบุใดฟิลด์หนึ่งไม่มีอยู่ ฟิลด์ที่ตรวจสอบอาจไม่มีอยู่หรือ为空
excluded_without_allเมื่อฟิลด์ที่ระบุทั้งหมดไม่มีอยู่ ฟิลด์ที่ตรวจสอบอาจไม่มีอยู่หรือ为空
uniqueตรวจสอบว่าแต่ละค่า arr, map, slice เป็นเอกลักษณ์

Alias

Tagคำอธิบาย
iscolorhexcolor|rgb|rgba|hsl|hsla
country_codeiso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric

Operators

TagคำอธิบายHex
,AND operation ใช้แท็กตรวจสอบหลายรายการ ต้องเป็นไปตามเงื่อนไขทั้งหมด ไม่มีช่องว่างระหว่างเครื่องหมายจุลภาค0x2c
|OR operation ใช้แท็กตรวจสอบหลายรายการ แต่ต้องเป็นไปตามเงื่อนไขใดเงื่อนไขหนึ่ง0x7c
-ข้ามการตรวจสอบฟิลด์นี้0x2d
=สัญลักษณ์การจับคู่พารามิเตอร์0x3d

TIP

เมื่อต้องการจับคู่ตัวดำเนินการในการตรวจสอบฟิลด์ ต้องใช้การแทนที่ด้วย hex สิบหก utf8 เช่น

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

การใช้งาน

ต่อไปนี้จะแนะนำการใช้งานพื้นฐานของ Validator พร้อมตัวอย่างโค้ด

Singleton

go
var validate *validator.Validate

ในการใช้งาน ทางทางการแนะนำให้ มีอินสแตนซ์ตัวตรวจสอบเพียงหนึ่งตัวตลอดวงจรชีวิตของโปรแกรม ซึ่งจะเป็นประโยชน์ต่อการแคชข้อมูล

สร้างตัวตรวจสอบ

เมื่อใช้ Validator แบบเดี่ยวโดยไม่รวมเฟรมเวิร์กอื่น ต้องสร้างตัวตรวจสอบด้วยตนเอง

go
validate = validator.New()

การตรวจสอบโครงสร้าง体

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

เมธอด Struct ใช้สำหรับตรวจสอบฟิลด์สาธารณะทั้งหมดของโครงสร้าง体 โดยค่าเริ่มต้นจะทำการตรวจสอบโครงสร้าง体แบบซ้อนโดยอัตโนมัติ เมื่อส่งค่าที่ไม่ถูกต้องหรือค่าเป็น nil จะส่งคืน InvalidValidationError หากการตรวจสอบล้มเหลวจะส่งคืน ValidationErrors

ตัวอย่าง

go
package validate

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

type User struct {
  Name    string `validate:"contains=jack"` //ชื่อต้องมี jack
  Age     int    `validate:"gte=18"`        //มากกว่าหรือเท่ากับ18ปี
  Address string `validate:"endswith=市"`      //ลงท้ายด้วย市
}

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

ผลลัพธ์

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

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

การตรวจสอบ map

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

ตรวจสอบ key-value ผ่านแท็ก map

ตัวอย่าง

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

ผลลัพธ์

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

การตรวจสอบ slice

ตรวจสอบ slice สตริง ก่อน dive คือ tag สำหรับตรวจสอบ slice หลัง dive คือ tag สำหรับตรวจสอบค่าใน slice slice ซ้อนก็เช่นเดียวกัน มีกี่มิติก็ใช้ dive กี่ตัว

go
func TestSlice1(t *testing.T) {
  list := []string{"jack", "mike", "lisa", "golang"}
  err := validator.New().Var(list, "max=5,dive,contains=a,min=5") //ความยาว slice สูงสุด5, องค์ประกอบต้องมีอักขระa, และความยาวต่ำสุด5
  fmt.Println(err)
}

ผลลัพธ์

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

ตรวจสอบโครงสร้าง体สำหรับผู้ใช้แต่ละคนใน 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" คือการตรวจสอบเชิงลึก เมื่อองค์ประกอบเป็นโครงสร้าง体 จะทำการตรวจสอบโครงสร้าง体โดยอัตโนมัติ
   fmt.Println(err)
}

ผลลัพธ์

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

การตรวจสอบตัวแปร

ค่อนข้างง่ายและเข้าใจได้ ไม่ต้องอธิบายมาก

ตัวอย่าง 1

go
func TestVar(t *testing.T) {
   name := "jack"
   err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") //ความยาวสูงสุด5, ความยาวต่ำสุด1, มีอักษรa, ลงท้ายด้วยl
   fmt.Println(err)
}

ผลลัพธ์

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

ตัวอย่าง 2

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

ผลลัพธ์

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

TIP

เมธอด Var สามารถตรวจสอบประเภทได้แก่ โครงสร้าง体, ตัวแปร, slice, map ต้องใช้แท็ก dive อย่างเหมาะสม

การตรวจสอบฟิลด์

พารามิเตอร์ของการตรวจสอบฟิลด์ไม่ใช่ประเภทพื้นฐานอีกต่อไป แต่เป็นชื่อฟิลด์ของโครงสร้าง体 อาจเป็นฟิลด์ของตัวเองหรือฟิลด์ของโครงสร้าง体แบบซ้อน

go
type Password struct {
   FirstPassword  string `validate:"eqfield=SecondPassword"` //ตรวจสอบว่ารหัสผ่านที่ป้อนสองครั้งเท่ากันหรือไม่
   SecondPassword string
}

type RegisterUser struct {
   Username string `validate:"necsfield=Password.FirstPassword"` //ในการลงทะเบียนเพื่อความปลอดภัย ห้ามรหัสผ่านและชื่อผู้ใช้เหมือนกัน
   Password Password
}

func TestCrossStructFieldValidate(t *testing.T) {
   validate = validator.New()
   // ล้มเหลว
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gopher",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gophers",
      },
   }))
   // สำเร็จ
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gophers",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gopher",
      },
   }))
}

ผลลัพธ์

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

เมื่อใช้การตรวจสอบฟิลด์ เมื่อฟิลด์หรือโครงสร้าง体ที่ Tag เป็นพารามิเตอร์ไม่มีอยู่ จะถือว่าล้มเหลวในการตรวจสอบทันที เช่น:

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

สำหรับข้อผิดพลาดการสะกดเช่นนี้ ตรวจสอบได้ยาก และการตรวจสอบจะแสดงเฉพาะว่าไม่ผ่าน ต้องระวังเป็นพิเศษ

ขั้นสูง

ต่อไปจะอธิบายเทคนิคการใช้งานขั้นสูงและการดำเนินการที่กำหนดเองมากขึ้น

นามธรรมที่กำหนดเอง

ในบางครั้ง สำหรับฟิลด์มีแท็กตรวจสอบจำนวนมาก เมื่อต้องการนำไปใช้กับฟิลด์อื่น คุณอาจคัดลอกและวางโดยตรง แต่นี่ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด วิธีที่ดีกว่าคือการลงทะเบียนนามธรรมเพื่อเพิ่มการใช้งานซ้ำ ดูตัวอย่างด้านล่าง:

go
var validate *validator.Validate

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

func TestAlias(t *testing.T) {
  validate = validator.New()
    // ลงทะเบียนนามธรรม
  validate.RegisterAlias("namerules", PERSON_NAME_RULES)
  type person struct {
    FirstName string `validate:"namerules"` // ใช้นามธรรม
    LastName  string `validate:"namerules"`
  }

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

  fmt.Println(err)
}

ผลลัพธ์

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

ฟังก์ชันตรวจสอบที่กำหนดเอง

แม้ว่าแท็กตรวจสอบที่มาพร้อมกับคอมโพเนนต์จะเพียงพอสำหรับสถานการณ์พื้นฐาน แต่บางครั้งสำหรับความต้องการพิเศษต้องกำหนดตรรกะเอง Validator มี API ที่เกี่ยวข้องสำหรับการกำหนดฟังก์ชันตรวจสอบ ดูตัวอย่างด้านล่าง:

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

สร้างฟังก์ชันเพื่อตรวจสอบว่าค่าฟิลด์เท่ากับ "666" หรือไม่ และ Tag ที่สอดคล้องกันคือ is666 ผลลัพธ์ดังนี้

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

TIP

หากแท็กที่ลงทะเบียนมีอยู่แล้ว จะถูกแทนที่ด้วยค่าใหม่ กล่าวคือ สามารถ "เขียนทับ" ตรรกะการตรวจสอบแท็กเริ่มต้นได้

ฟังก์ชันตรวจสอบประเภทที่กำหนดเอง

ฟังก์ชันตรวจสอบประเภทมีไว้สำหรับประเภทใดประเภทหนึ่งโดยเฉพาะ มักใช้สำหรับประเภทที่ไม่ใช่ประเภทพื้นฐาน และสามารถทับการตรวจสอบประเภทพื้นฐานเริ่มต้นได้ ดูตัวอย่างด้านล่าง:

go
type Address struct {
  name string
}

func TestCustomTypeValidate(t *testing.T) {
  validate = validator.New()
  validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // ลงทะเบียนฟังก์ชันตรวจสอบประเภทและประเภทที่สอดคล้องกัน
  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 {
    //การจัดการข้อผิดพลาด
    if address.name == "" {
      return address.name
    }

    return value //ส่งคืนฟิลด์แสดงว่าการตรวจสอบถูกต้อง
  }
  return nil
}

ผลลัพธ์

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

TIP

การลงทะเบียนหลายประเภทกับฟังก์ชันเดียวกันก็เป็นเช่นเดียวกัน

ฟังก์ชันตรวจสอบโครงสร้าง体ที่กำหนดเอง

ความแตกต่างของฟังก์ชันตรวจสอบโครงสร้าง体คือ พารามิเตอร์ของฟังก์ชันอื่นเป็นฟิลด์ แต่พารามิเตอร์ของฟังก์ชันนี้เป็นโครงสร้าง体 ดูตัวอย่างด้านล่าง:

go
type People struct {
   FirstName string
   LastName  string
}

func TestCustomStructLevel(t *testing.T) {
   validate = validator.New()
   validate.RegisterStructValidation(PeopleValidate, People{}) //เช่นเดียวกับการลงทะเบียนประเภท สามารถส่งโครงสร้าง体ได้มากกว่าหนึ่งประเภท
   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", "", "")
   }
}

ผลลัพธ์

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

หลายภาษา

คอมโพเนนต์ตัวแปล

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

คอมโพเนนต์ภูมิภาค

go get github.com/go-playground/locales

ภาษาเริ่มต้นของตัวตรวจสอบคือภาษาอังกฤษ และในการพัฒนาโปรเจกต์อาจใช้มากกว่าหนึ่งภาษา ในเวลานี้เราต้องใช้คอมโพเนนต์หลายภาษา ดูตัวอย่างด้านล่าง:

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"` //ชื่อต้องมี jack
   Age     int    `validate:"gte=18"`        //มากกว่าหรือเท่ากับ18ปี
   Address string `validate:"endswith=市"`    //ลงท้ายด้วย市
}

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

func TestTranslate(t *testing.T) {
   zh := zh.New()
   //ตัวแรกคือตัวสำรอง ต่อมาคือภาษาที่รองรับ สามารถมีได้หลายภาษา
   uni = ut.New(zh, zh)
   //ภาษาที่นี่โดยปกติสามารถรับจาก Accept-Language ในส่วนหัวของการร้องขอ http
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) //ลงทะเบียนตัวแปลเริ่มต้น
   }
   err := validate.Struct(User{
      Name:    "",
      Age:     0,
      Address: "",
   })
   fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}

ผลลัพธ์

map[User.Address:Address必须以文本'市'结尾 User.Age:Age必须大于或等于18 User.Name:Name必须包含文本'jack']

ยังสามารถแปลข้อผิดพลาดแต่ละรายการแยกกัน

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

ผลลัพธ์

Name必须包含文本'jack'
Age必须大于或等于18
Address必须以文本'市'结尾

จะเห็นว่าค่าที่ส่งกลับเป็น map เห็นได้ว่าการแปลข้อความข้อผิดพลาดพื้นฐานทำได้แล้ว แต่ยังไม่เพียงพอที่จะนำไปใช้ ต้องตกแต่งข้อความข้อผิดพลาดเพื่อให้ติดต่อกับลูกค้าหรือ frontend ได้ดีขึ้น

go
type User struct {

   Name    string `validate:"contains=jack" label:"姓名"` //ชื่อต้องมี jack
   Age     int    `validate:"gte=18" label:"年龄"`        //มากกว่าหรือเท่ากับ18ปี
   Address string `validate:"endswith=市" label:"地址"`    //ลงท้ายด้วย市
   Sex     string `validate:"required" label:"性别"`
}

ก่อนอื่นกำหนดแท็ก label เอง ค่าของมันคือชื่อภาษาจีนของฟิลด์ จากนั้นลงทะเบียน TagNameFunc ผ่านตัวตรวจสอบ หน้าที่ของมันคือรับชื่อฟิลด์หรือแทนที่ชื่อเดิม ในไฟล์ errors.go คำอธิบายประกอบบนเมธอด Filed() string กล่าวว่า "ชื่อฟิลด์ที่มีชื่อแท็กมีความสำคัญกว่าชื่อฟิลด์จริง" ดังนั้นเมื่อเกิดข้อผิดพลาด สามารถใช้ชื่อภาษาจีนที่กำหนดเองแทนคำภาษาอังกฤษ TagNameFunc ดังนี้:

go
// เราเพิ่มแท็กที่กำหนดเอง แท็กนี้ใช้สำหรับชื่อภาษาจีนของฟิลด์โครงสร้าง体 จะแทนที่ชื่อฟิลด์เดิม
func CustomTagNameFunc(field reflect.StructField) string {
   label := field.Tag.Get("label")
   if len(label) == 0 {
      return field.Name
   }
   return label
}

สุดท้ายลงทะเบียน

go
validate.RegisterTagNameFunc(CustomTagNameFunc)

ดำเนินการอีกครั้ง

姓名必须包含文本'jack'
年龄必须大于或等于18
地址必须以文本'市'结尾

แต่ยังไม่เพียงพอ ยังไม่เพียงพอที่จะส่งคืนเป็นข้อความข้อผิดพลาดให้ frontend คุณอาจคิดที่จะ serialize map เป็น json นี่เป็นวิธีแก้ปัญหา แต่คุณอาจได้ผลลัพธ์ดังนี้:

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

โดยการประมวลผล key ของ map ได้ผลลัพธ์ดังนี้:

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

แต่ไม่แนะนำให้ส่งคืนข้อมูลข้างต้นให้ frontend เราสามารถประมวลผลเป็นสตริงเพื่อส่งคืนเป็นข้อความ

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

โค้ดสมบูรณ์

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:"姓名"` //ชื่อต้องมี jack
   Age     int    `validate:"gte=18" label:"年龄"`        //มากกว่าหรือเท่ากับ18ปี
   Address string `validate:"endswith=市" label:"地址"`    //ลงท้ายด้วย市
   Sex     string `validate:"required" label:"性别"`
}

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

// เราเพิ่มแท็กที่กำหนดเอง แท็กนี้ใช้สำหรับชื่อภาษาจีนของฟิลด์โครงสร้าง体 จะแทนที่ชื่อฟิลด์เดิม
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)
   //ภาษาที่นี่โดยปกติสามารถรับจาก Accept-Language ในส่วนหัวของการร้องขอ http
   trans, found := uni.GetTranslator(zh.Locale())
   validate = validator.New()
   if found {
      zh_trans.RegisterDefaultTranslations(validate, trans) //ลงทะเบียนตัวแปลเริ่มต้น
   }
   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()
}

สุดท้าย หากคิดว่าข้อความข้อผิดพลาดเย็นชาเกินไป และต้องการให้เป็นมิตรมากขึ้น สามารถเขียนทับข้อความข้อผิดพลาดของแท็กที่ระบุได้ ต้องใช้เมธอด RegisterTranslation และต้องใช้ฟังก์ชันสองประเภท ประเภทแรกคือ RegisterTranslationsFunc รับผิดชอบการลงทะเบียนเทมเพลตการแปลสำหรับ Tag ที่สอดคล้องกัน อีกประเภทคือ TranslationFunc รับผิดชอบการประมวลผลเทมเพลตเพื่อให้ได้การแปลสุดท้าย ที่นี่ใช้ required เป็นตัวอย่าง:

go
func requiredOverrideRegister(ut ut.Translator) error { //หน้าที่ของฟังก์ชันนี้คือการลงทะเบียนเทมเพลตการแปล
  return ut.Add("required", "{}เป็นฟิลด์ที่ต้องกรอก", true) // {} เป็น placeholder true หมายถึงการเขียนทับเทมเพลตที่มีอยู่
}

func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // หน้าที่ของฟังก์ชันนี้คือรับผิดชอบการแปลเนื้อหา
  t, _ := ut.T("required", fe.Field()) //พารามิเตอร์สามารถมีได้หลายตัว ขึ้นอยู่กับจำนวน placeholder ในเทมเพลต Tag ที่ลงทะเบียนที่สอดคล้องกัน
  return t
}

สุดท้ายลงทะเบียน

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

ผลลัพธ์

姓名必须包含文本'back', 年龄必须大于或等于18, 地址必须以文本'市'结尾, 性别是一个必须填写的字段,

ไฟล์ภาษา

จริงๆ แล้วการลงทะเบียนโค้ดทีละตัวค่อนข้างยุ่งยาก universal-translator รองรับการใช้ไฟล์การกำหนดค่า 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") //แนะนำให้导入หลังลงทะเบียน เพื่อทับแท็กเดิม
   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

json
[
  {
    "locale": "zh",
    "key": "required",
    "trans": "这是一个十分重要的字段{0},你必须填写它",
    "override": true
  }
]

ผลลัพธ์

map[Gopher.Language:这是一个十分重要的字段Language,你必须填写它]

TIP

universal-translator มีกับดักมากมายในการใช้ หากต้องการทับ Tag เดิม type และ rule可以不กรอก เพราะในการกำหนดค่าเดิมไม่ได้กรอก ควร保持一致 หากกรอก type อะไร จะเพิ่มการกำหนดค่าลงใน map ที่สอดคล้องกัน หากเป็น Cardinal หรือ type อื่นๆ และ rule กำหนดค่า one หรืออื่นๆ ต้องทำการกำหนดค่าที่สอดคล้องกันในท้องถิ่นจึงจะใช้งานได้ตามปกติ มิฉะนั้นจะเกิดข้อผิดพลาด

Golang by www.golangdev.cn edit