Skip to content

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ênXMLYAMLJSONProtocol Buffers
Cấu trúc dữ liệuPhức tạpKhá đơn giảnĐơn giảnKhá phức tạp
Cách lưuVăn bảnVăn bảnVăn bảnNhị phân
Kích thước lưuLớnTrung bìnhTrung bìnhNhỏ
Hiệu suất phân tíchChậmTrung bìnhTrung bìnhNhanh
Hỗ trợ ngôn ngữRất nhiềuNhiềuRất nhiềuKhá nhiều
Độ khó phát triểnPhức tạpKhá đơn giảnĐơn giảnĐơn giản
Chi phí học tậpThấpThấpThấpThấp
Phạm vi áp dụngTrao đổi dữ liệuTệp cấu hìnhTrao đổi dữ liệuTrao đổ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

go
type Person struct {
   UserId   string `xml:"id"`
   Username string `xml:"name"`
   Age      int    `xml:"age"`
   Address  string `xml:"address"`
}
go
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 //deserialization

Serialization

go
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

xml
<Person>
        <id>120</id>
        <name>jack</name>
        <age>18</age>
        <address>usa</address>
</Person>

Deserialization

go
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

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

powershell
go get github.com/go-yaml/yaml

Phương thức chính

go
func Marshal(in interface{}) (out []byte, err error) //serialization

func Unmarshal(in []byte, out interface{}) (err error) //deserialization

Trước hết chuẩn bị struct

go
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

yaml
database: mysql
url: 127.0.0.1
port: 3306
username: root
password: 123456

Serialization

go
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

yaml
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

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

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

Trước hết định nghĩa struct

go
type Person struct {
   UserId   string
   Username string
   Age      int
   Address  string
}

Serialization

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

json
{ "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.

go
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

json
{ "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.

go
func MarshalIndent(v any, prefix, indent string) ([]byte, error)
go
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

json
{
  "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.

go
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

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

Tệp person.proto

protobuf
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

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

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

Golang by www.golangdev.cn edit