Thứ năm, 31/10/2019 | 00:00 GMT+7

Cách sử dụng thẻ cấu trúc trong Go

Cấu trúc, hoặc cấu trúc, được sử dụng để thu thập nhiều phần thông tin cùng nhau trong một đơn vị. Những bộ sưu tập thông tin này được sử dụng để mô tả các khái niệm cấp cao hơn, chẳng hạn như Address bao gồm Street , City , State và Mã PostalCode . Khi bạn đọc thông tin này từ các hệ thống như database hoặc API, bạn có thể sử dụng thẻ struct để kiểm soát cách thông tin này được gán cho các trường của cấu trúc. Thẻ cấu trúc là các mẩu metadata nhỏ được gắn vào các trường của cấu trúc cung cấp hướng dẫn cho mã Go khác hoạt động với cấu trúc.

Thẻ cấu trúc trông như thế nào?

Thẻ Go struct là những chú thích xuất hiện sau loại trong khai báo Go struct. Mỗi thẻ bao gồm các chuỗi ngắn được liên kết với một số giá trị tương ứng.

Thẻ struct trông như thế này, với phần bù thẻ bằng các ký tự backtick ` :

type User struct {     Name string `example:"name"` } 

Mã Go khác sau đó có khả năng kiểm tra các cấu trúc này và extract các giá trị được gán cho các khóa cụ thể mà nó yêu cầu. Thẻ cấu trúc không ảnh hưởng đến hoạt động của mã của bạn nếu không có một số mã khác kiểm tra chúng.

Hãy thử ví dụ này để xem các thẻ struct trông như thế nào và nếu không có mã từ gói khác, chúng sẽ không có tác dụng.

package main  import "fmt"  type User struct {     Name string `example:"name"` }  func (u *User) String() string {     return fmt.Sprintf("Hi! My name is %s", u.Name) }  func main() {     u := &User{         Name: "Sammy",     }      fmt.Println(u) } 

Điều này sẽ xuất ra:

Output
Hi! My name is Sammy

Ví dụ này xác định kiểu User với trường Name . Trường Name đã được cấp một thẻ cấu trúc, example:"name" . Ta đề cập đến thẻ cụ thể này trong cuộc trò chuyện là “thẻ cấu trúc mẫu” vì nó sử dụng từ “ví dụ” làm khóa của nó. Các example thẻ struct có giá trị "name" cho Name trường. Trên kiểu người User , ta cũng xác định phương thức String() theo yêu cầu của giao diện fmt.Stringer . Điều này sẽ được gọi tự động khi ta chuyển kiểu cho fmt.Println và cho ta cơ hội tạo ra một version có định dạng độc đáo cho cấu trúc của ta .

Trong phần thân của main , ta tạo một version mới của kiểu User của ta và chuyển nó đến fmt.Println . Mặc dù struct đã có thẻ struct, nhưng ta thấy rằng nó không ảnh hưởng đến hoạt động của mã Go này. Nó sẽ hoạt động giống hệt như vậy nếu không có thẻ struct.

Để sử dụng thẻ struct để thực hiện điều gì đó, mã Go khác phải được viết để kiểm tra cấu trúc trong thời gian chạy. Thư viện chuẩn có các gói sử dụng thẻ struct như một phần hoạt động của chúng. Phổ biến nhất trong số này là gói encoding/json .

Mã hóa JSON

JavaScript Object Notation (JSON) là một định dạng văn bản để mã hóa các bộ sưu tập dữ liệu được tổ chức theo các khóa chuỗi khác nhau. Nó thường được sử dụng để giao tiếp dữ liệu giữa các chương trình khác nhau vì định dạng đủ đơn giản để các thư viện tồn tại để giải mã nó bằng nhiều ngôn ngữ khác nhau. Sau đây là một ví dụ về JSON:

{   "language": "Go",   "mascot": "Gopher" } 

Đối tượng JSON này chứa hai khóa, languagemascot . Theo sau các khóa này là các giá trị được liên kết. Ở đây khóa language có giá trị là Gomascot được gán giá trị là Gopher .

Bộ mã hóa JSON trong thư viện chuẩn sử dụng thẻ struct làm chú thích chỉ ra cho bộ mã hóa cách bạn muốn đặt tên cho các trường của bạn trong kết quả JSON. Các cơ chế mã hóa và giải mã JSON này có thể được tìm thấy trong gói encoding/json .

Hãy thử ví dụ này để xem cách JSON được mã hóa mà không có thẻ cấu trúc:

package main  import (     "encoding/json"     "fmt"     "log"     "os"     "time" )  type User struct {     Name          string     Password      string     PreferredFish []string     CreatedAt     time.Time }  func main() {     u := &User{         Name:      "Sammy the Shark",         Password:  "fisharegreat",         CreatedAt: time.Now(),     }      out, err := json.MarshalIndent(u, "", "  ")     if err != nil {         log.Println(err)         os.Exit(1)     }      fmt.Println(string(out)) } 

Thao tác này sẽ in ra kết quả sau:

Output
{ "Name": "Sammy the Shark", "Password": "fisharegreat", "CreatedAt": "2019-09-23T15:50:01.203059-04:00" }

Ta đã xác định một cấu trúc mô tả một user với các trường bao gồm tên, password và thời gian user được tạo. Trong hàm main , ta tạo một version của user này bằng cách cung cấp các giá trị cho tất cả các trường ngoại trừ PreferredFish (Sammy thích tất cả các loại cá). Sau đó, ta chuyển thể hiện của User cho hàm json.MarshalIndent . Điều này được sử dụng để ta có thể dễ dàng xem kết quả JSON hơn mà không cần sử dụng công cụ định dạng bên ngoài. Cuộc gọi này có thể được thay thế bằng json.Marshal(u) để nhận JSON mà không có thêm bất kỳ khoảng trắng nào. Hai đối số bổ sung cho json.MarshalIndent kiểm soát tiền tố đến kết quả (mà ta đã bỏ qua với chuỗi trống) và các ký tự để sử dụng để thụt lề, đây là hai ký tự khoảng trắng. Bất kỳ lỗi nào được tạo ra từ json.MarshalIndent đều được ghi lại và chương trình kết thúc bằng cách sử dụng os.Exit(1) . Cuối cùng, ta truyền []byte trả về từ json.MarshalIndent thành một string và chuyển chuỗi kết quả cho fmt.Println để in trên terminal .

Các trường của cấu trúc xuất hiện chính xác như ta đã đặt tên cho chúng. Đây không phải là kiểu JSON điển hình mà bạn có thể mong đợi, kiểu này sử dụng cách viết hoa camel cho tên các trường. Bạn sẽ thay đổi tên của trường theo kiểu trường hợp lạc đà trong ví dụ tiếp theo này. Như bạn sẽ thấy khi chạy ví dụ này, điều này sẽ không hoạt động vì tên trường mong muốn xung đột với các luật của Go về tên trường được xuất.

package main  import (     "encoding/json"     "fmt"     "log"     "os"     "time" )  type User struct {     name          string     password      string     preferredFish []string     createdAt     time.Time }  func main() {     u := &User{         name:      "Sammy the Shark",         password:  "fisharegreat",         createdAt: time.Now(),     }      out, err := json.MarshalIndent(u, "", "  ")     if err != nil {         log.Println(err)         os.Exit(1)     }      fmt.Println(string(out)) } 

Điều này sẽ hiển thị kết quả sau:

Output
{}

Trong version này, ta đã thay đổi tên của các trường có vỏ lạc đà. Bây giờ Namename , Passwordpassword , và cuối cùng CreatedAt được createdAt . Trong phần nội dung của main ta đã thay đổi cách khởi tạo cấu trúc của bạn để sử dụng những tên mới này. Sau đó, ta chuyển cấu trúc cho hàm json.MarshalIndent như trước. Đầu ra, lần này là một đối tượng JSON trống, {} .

Các trường viết hoa camel đúng cách yêu cầu ký tự đầu tiên phải được viết hoa thấp hơn. Mặc dù JSON không quan tâm đến cách bạn đặt tên cho các trường của bạn , nhưng Go thì có, vì nó cho biết khả năng hiển thị của trường bên ngoài gói. Vì gói encoding/json là một gói riêng biệt với gói main mà ta đang sử dụng, ta phải viết hoa ký tự đầu tiên để encoding/json hiển thị. Có vẻ như ta đang gặp bế tắc và ta cần một số cách để truyền tải đến bộ mã hóa JSON những gì ta muốn đặt tên trường này.

Sử dụng thẻ cấu trúc để kiểm soát mã hóa

Bạn có thể sửa đổi ví dụ trước để có các trường đã xuất được mã hóa đúng với tên trường có vỏ lạc đà bằng cách chú thích mỗi trường bằng thẻ struct. Thẻ struct mà encoding/json nhận ra có khóa là json và một giá trị kiểm soát kết quả . Bằng cách đặt version dựa trên lạc đà của tên trường làm giá trị cho khóa json , bộ mã hóa sẽ sử dụng tên đó thay thế. Ví dụ này khắc phục hai lần thử trước:

package main  import (     "encoding/json"     "fmt"     "log"     "os"     "time" )  type User struct {     Name          string    `json:"name"`     Password      string    `json:"password"`     PreferredFish []string  `json:"preferredFish"`     CreatedAt     time.Time `json:"createdAt"` }  func main() {     u := &User{         Name:      "Sammy the Shark",         Password:  "fisharegreat",         CreatedAt: time.Now(),     }      out, err := json.MarshalIndent(u, "", "  ")     if err != nil {         log.Println(err)         os.Exit(1)     }      fmt.Println(string(out)) } 

Điều này sẽ xuất ra:

Output
{ "name": "Sammy the Shark", "password": "fisharegreat", "preferredFish": null, "createdAt": "2019-09-23T18:16:17.57739-04:00" }

Ta đã thay đổi tên trường trở lại để hiển thị với các gói khác bằng cách viết hoa các chữ cái đầu tiên trong tên của chúng. Tuy nhiên, lần này ta đã thêm các thẻ struct ở dạng json:"name" , trong đó "name" là tên mà ta muốn json.MarshalIndent sử dụng khi in struct của ta dưới dạng JSON.

Bây giờ ta đã định dạng thành công JSON của bạn một cách chính xác. Tuy nhiên, lưu ý các trường cho một số giá trị đã được in mặc dù ta không đặt các giá trị đó. Bộ mã hóa JSON cũng có thể loại bỏ các trường này, nếu bạn muốn.

Xóa các trường JSON trống

Thông thường nhất, ta muốn loại bỏ các trường xuất chưa được đặt trong JSON. Vì tất cả các loại trong Go đều có “giá trị bằng không”, một số giá trị mặc định mà chúng được đặt thành, gói encoding/json cần thông tin bổ sung để có thể cho biết rằng một số trường nên được coi là chưa được đặt khi nó giả định giá trị 0 này. Trong phần giá trị của bất kỳ thẻ cấu trúc json nào, bạn có thể thêm vào tên trường mong muốn của bạn bằng ,omitempty để yêu cầu bộ mã hóa JSON chặn kết quả của trường này khi trường được đặt thành giá trị 0. Ví dụ sau sửa các ví dụ trước để không còn xuất các trường trống:

package main  import (     "encoding/json"     "fmt"     "log"     "os"     "time" )  type User struct {     Name          string    `json:"name"`     Password      string    `json:"password"`     PreferredFish []string  `json:"preferredFish,omitempty"`     CreatedAt     time.Time `json:"createdAt"` }  func main() {     u := &User{         Name:      "Sammy the Shark",         Password:  "fisharegreat",         CreatedAt: time.Now(),     }      out, err := json.MarshalIndent(u, "", "  ")     if err != nil {         log.Println(err)         os.Exit(1)     }      fmt.Println(string(out)) } 

Ví dụ này sẽ xuất ra:

Output
{ "name": "Sammy the Shark", "password": "fisharegreat", "createdAt": "2019-09-23T18:21:53.863846-04:00" }

Ta đã sửa đổi các ví dụ trước đó để trường PreferredFish giờ có thẻ struct json:"preferredFish,omitempty" . Sự hiện diện của tăng cường ,omitempty khiến bộ mã hóa JSON bỏ qua trường đó, vì ta quyết định không đặt nó. Điều này có giá trị null trong kết quả của ví dụ trước của ta .

Đầu ra này trông tốt hơn nhiều, nhưng ta vẫn đang in ra password của user . Gói encoding/json cung cấp một cách khác để ta hoàn toàn bỏ qua các trường riêng tư.

Bỏ qua các trường riêng tư

Một số trường phải được xuất từ cấu trúc để các gói khác có thể tương tác chính xác với kiểu. Tuy nhiên, bản chất của các trường này có thể nhạy cảm, vì vậy trong những trường hợp này, ta muốn bộ mã hóa JSON hoàn toàn bỏ qua trường — ngay cả khi nó được đặt. Điều này được thực hiện bằng cách sử dụng giá trị đặc biệt - làm đối số giá trị cho thẻ json: struct.

Ví dụ này khắc phục sự cố lộ password của user .

package main  import (     "encoding/json"     "fmt"     "log"     "os"     "time" )  type User struct {     Name      string    `json:"name"`     Password  string    `json:"-"`     CreatedAt time.Time `json:"createdAt"` }  func main() {     u := &User{         Name:      "Sammy the Shark",         Password:  "fisharegreat",         CreatedAt: time.Now(),     }      out, err := json.MarshalIndent(u, "", "  ")     if err != nil {         log.Println(err)         os.Exit(1)     }      fmt.Println(string(out)) } 

Khi bạn chạy ví dụ này, bạn sẽ thấy kết quả này:

Output
{ "name": "Sammy the Shark", "createdAt": "2019-09-23T16:08:21.124481-04:00" }

Điều duy nhất mà ta đã thay đổi trong ví dụ này so với những ví dụ trước là trường password hiện sử dụng giá trị "-" đặc biệt cho thẻ json: struct của nó. Ta thấy rằng trong kết quả từ ví dụ này, trường password không còn nữa.

Các tính năng này của gói encoding/json ,omitempty"-" , không phải là tiêu chuẩn. Những gì một gói quyết định thực hiện với các giá trị của thẻ struct phụ thuộc vào việc triển khai nó. Vì gói encoding/json là một phần của thư viện chuẩn, các gói khác cũng đã triển khai các tính năng này theo cách tương tự như một vấn đề quy ước. Tuy nhiên, điều quan trọng là phải đọc tài liệu cho bất kỳ gói bên thứ ba nào sử dụng thẻ struct để tìm hiểu những gì được hỗ trợ và những gì không.

Kết luận

Thẻ cấu trúc cung cấp một phương tiện mạnh mẽ để tăng cường chức năng của mã hoạt động với cấu trúc của bạn. Nhiều thư viện chuẩn và các gói của bên thứ ba cung cấp các cách để tùy chỉnh hoạt động của chúng thông qua việc sử dụng các thẻ struct. Sử dụng chúng một cách hiệu quả trong mã của bạn cung cấp cả hành vi tùy chỉnh này và tài liệu ngắn gọn cách các trường này được sử dụng cho các nhà phát triển trong tương lai.


Tags:

Các tin liên quan