Mã hóa và giải mã trong Go
Trong thời đại internet hiện nay, các định dạng dữ liệu độc lập với ngôn ngữ thường dùng nhất là xml, Yaml, json, protobuf, Go cũng hỗ trợ các thao tác liên quan đến các định dạng dữ liệu này, dưới đây là bảng so sánh.
| Tên | XML | YAML | JSON | Protocol Buffers |
|---|---|---|---|---|
| Cấu trúc dữ liệu | Phức tạp | Khá đơn giản | Đơn giản | Khá phức tạp |
| Cách lưu | Văn bản | Văn bản | Văn bản | Nhị phân |
| Kích thước lưu | Lớn | Trung bình | Trung bình | Nhỏ |
| Hiệu suất phân tích | Chậm | Trung bình | Trung bình | Nhanh |
| Hỗ trợ ngôn ngữ | Rất nhiều | Nhiều | Rất nhiều | Khá nhiều |
| Độ khó phát triển | Phức tạp | Khá đơn giản | Đơn giản | Đơn giản |
| Chi phí học tập | Thấp | Thấp | Thấp | Thấp |
| Phạm vi áp dụng | Trao đổi dữ liệu | Tệp cấu hình | Trao đổi dữ liệu | Trao đổi dữ liệu |
TIP
Trong Go, nếu muốn thực hiện serialization và deserialization cho struct, các trường phải được export, tức là chữ cái đầu viết hoa.
Ngoài ra, TOML cũng đang dần trở nên phổ biến, cú pháp giống như cải tiến của .ini, nếu quan tâm có thể truy cập TOML: Tom's (Semantic) Obvious, (Configuration) Minimal Language để tìm hiểu thêm.
XML
xml còn gọi là eXtensible Markup Language, là một định dạng dùng để lưu trữ dữ liệu, ra đời vào những năm 1960, là một trong những định dạng dữ liệu lâu đời nhất trong các định dạng trên. Nó có nhiều công dụng, có thể dùng cho truyền tải mạng, trao đổi dữ liệu, tệp cấu hình, lưu trữ dữ liệu, v.v. Nhưng theo sự thay đổi của thời đại, dần đang được thay thế bằng các ngôn ngữ đánh dấu mới.
Trước hết định nghĩa struct
type Person struct {
UserId string `xml:"id"`
Username string `xml:"name"`
Age int `xml:"age"`
Address string `xml:"address"`
}func Marshal(v any) ([]byte, error) //xml serialization
func MarshalIndent(v any, prefix, indent string) ([]byte, error) //định dạng
func Unmarshal(data []byte, v any) error //deserializationSerialization
func main() {
person := Person{
UserId: "120",
Username: "jack",
Age: 18,
Address: "usa",
}
bytes, err := xml.MarshalIndent(person, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}Kết quả xuất
<Person>
<id>120</id>
<name>jack</name>
<age>18</age>
<address>usa</address>
</Person>Deserialization
func main() {
var person = Person{
UserId: "",
Username: "",
Age: 0,
Address: "",
}
xmlStr := "<Person> \n <id>120</id> \n <name>jack</name> \n <age>18</age> \n <address>usa</address>\n</Person> "
err := xml.Unmarshal([]byte(xmlStr), &person)
if err != nil {
fmt.Println(err)
return
}
}Kết quả xuất
{UserId:120 Username:jack Age:18 Address:usa}Tuy nhiên cách phân tích xml truyền thống thường cần tạo struct mới, điều này sẽ rất phức tạp, hiện tại thường phân tích các cấu trúc xml đơn giản, nếu sử dụng cấu trúc phức tạp sẽ khiến người ta rất đau đầu. Vì vậy chúng ta thường dùng một thư viện mã nguồn mở bên thứ ba etree để phân tích xml, nếu quan tâm có thể tự tìm hiểu: Go plugin phân tích tệp xml khá tốt etree - Juejin (juejin.cn).
YML
Cú pháp của YAML tương tự như các ngôn ngữ cao cấp khác, và có thể biểu diễn đơn giản danh sách, bảng băm, scalar và các dạng dữ liệu khác. Nó sử dụng thụt lề bằng khoảng trắng và nhiều đặc điểm dựa trên giao diện, đặc biệt thích hợp dùng để biểu diễn hoặc chỉnh sửa cấu trúc dữ liệu, các tệp cấu hình khác nhau, YML cũng tồn tại dưới dạng tệp cấu hình trong nhiều dự án, cấu trúc nội dung của nó đơn giản hơn, dễ nhìn. Go chính thức không cung cấp hỗ trợ cho YML, chúng ta cần sử dụng gói bên thứ ba.
go get github.com/go-yaml/yamlPhương thức chính
func Marshal(in interface{}) (out []byte, err error) //serialization
func Unmarshal(in []byte, out interface{}) (err error) //deserializationTrước hết chuẩn bị struct
type Config struct {
Database string `yaml:"database"`
Url string `yaml:"url"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}Tệp cấu hình
database: mysql
url: 127.0.0.1
port: 3306
username: root
password: 123456Serialization
func main() {
config := Config{
Database: "oracle",
Url: "localhost",
Port: 3326,
Username: "root",
Password: "123456",
}
out, err := yaml.Marshal(config)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(out))
}Kết quả xuất
database: oracle
url: localhost
port: 3326
username: root
password: "123456"Tuy nhiên do bản thân yml có cú pháp thụt lề nghiêm ngặt, nên cũng không tồn tại vấn đề định dạng serialization.
Deserialization
func main() {
bytes, err := os.ReadFile("./src/config.yml")
if err != nil {
fmt.Println(err)
return
}
var config Config
err = yaml.Unmarshal(bytes, &config)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(config)
}Kết quả xuất
{mysql 127.0.0.1 3306 root 123456}JSON
json thường được dùng trong giao tiếp interface của phong cách Restful, so với xml nó nhẹ hơn, chi phí học tập thấp khiến nó trở thành định dạng trao đổi dữ liệu chính trong lĩnh vực web.
Trong Go, gói encoding/json cung cấp các hàm tương ứng để thực hiện serialization và deserialization json, các hàm chính được sử dụng như sau.
func Marshal(v any) ([]byte, error) //serialize Go object thành chuỗi json
func Unmarshal(data []byte, v any) error //deserialize chuỗi json thành Go objectTrước hết định nghĩa struct
type Person struct {
UserId string
Username string
Age int
Address string
}Serialization
func main() {
person := Person{
UserId: "120",
Username: "jack",
Age: 18,
Address: "usa",
}
bytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}Kết quả
{ "UserId": "120", "Username": "jack", "Age": 18, "Address": "usa" }Đổi tên trường
Chúng ta có thể đạt được hiệu quả đổi tên thông qua struct tag.
type Person struct {
UserId string `json:"id"`
Username string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}Lúc này kết quả xuất
{ "id": "1202", "name": "jack", "age": 19, "address": "USA" }Thụt lề
Mặc định khi serialization không có bất kỳ thụt lề nào, đây là để giảm thiểu hao phí không gian trong quá trình truyền tải, nhưng điều này không thuận lợi cho việc quan sát của con người, trong một số trường hợp chúng ta cần serialize nó thành dạng mà con người có thể quan sát được. Vì vậy, chỉ cần đổi một hàm.
func MarshalIndent(v any, prefix, indent string) ([]byte, error)func main() {
person := Person{
UserId: "1202",
Username: "jack",
Age: 19,
Address: "USA",
}
bytes, err := json.MarshalIndent(person, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(bytes))
}Kết quả xuất như sau
{
"id": "1202",
"name": "jack",
"age": 19,
"address": "USA"
}Deserialization
Khi deserialization cần lưu ý, nếu struct có json tag, thì ưu tiên lấy theo json tag, nếu không thì lấy theo tên thuộc tính của struct.
func main() {
person := Person{}
jsonStr := "{\"id\":\"120\",\"name\":\"jack\",\"age\":18,\"address\":\"usa\"}\n"
err := json.Unmarshal([]byte(jsonStr), &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v", person)
}Kết quả xuất
{UserId:120 Username:jack Age:18 Address:usa}Protocol Buffers
protocol là cơ chế serialization dữ liệu có cấu trúc trung lập với ngôn ngữ, trung lập với giao thức, có thể mở rộng được Google mã nguồn mở năm 2008. So với ba loại trên nó nhẹ hơn, và nhanh hơn khi đóng gói và mở gói, thường dùng trong lĩnh vực RPC, bài giảng về Protobuf có thể đến Protobuf。
Cài đặt phụ thuộc
go get github.com/golang/protobuf/protoTệp person.proto
syntax = "proto3";
option go_package = "./;person";
package proto;
enum Gender{
MAIL = 0;
FE_MAIL = 1;
}
message person {
string name = 1;
int32 age = 2;
Gender gender = 3;
}Sau khi tạo tệp
package main
import (
p "GoProject/src/proto"
"fmt"
"github.com/golang/protobuf/proto"
)
func main() {
person := p.Person{
Name: "wyh",
Age: 12,
Gender: p.Gender_FE_MAIL,
}
data, err := proto.Marshal(&person)//serialization
if err != nil {
fmt.Println(err)
return
}
temp := &p.Person{}
fmt.Println("proto buffer len: ", len(data), "bytes:", data)
err = proto.Unmarshal(data, temp)//deserialization
if err != nil {
fmt.Println(err)
return
}
fmt.Println(temp)
}Kết quả xuất
proto buffer len: 9 bytes: [10 3 119 121 104 16 12 24 1]
name:"wyh" age:12 gender:FE_MAILTuy nhiên thông thường chúng ta sẽ không serialize thủ công, trình biên dịch protoc có thể tạo mã nguồn của ngôn ngữ tương ứng dựa trên tệp proto mà chúng ta đã định nghĩa.
