Skip to content

การเข้ารหัสและการถอดรหัส encode ในภาษา Go

ในยุคอินเทอร์เน็ตปัจจุบัน รูปแบบข้อมูลที่เป็นอิสระจากภาษาที่ใช้บ่อยที่สุดคือ xml, Yaml, json, protobuf และ Go ก็รองรับการดำเนินการที่เกี่ยวข้องกับรูปแบบข้อมูลเหล่านี้เช่นกัน ตารางเปรียบเทียบมีดังนี้

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

TIP

ใน Go หากต้องการทำ serialization และ deserialization กับ struct ฟิลด์จะต้องเปิดเผยภายนอก กล่าวคือตัวอักษรแรกต้องเป็นตัวพิมพ์ใหญ่

นอกจากนี้ TOML ก็เริ่มได้รับความนิยมเช่นกัน ไวยากรณ์คล้ายกับการปรับปรุง .ini สนใจสามารถไปที่ TOML: Tom's Obvious, Minimal Language เพื่อเรียนรู้เพิ่มเติม

XML

xml หรือชื่อเต็มคือ eXtensible Markup Language เป็นรูปแบบที่ใช้สำหรับเก็บข้อมูล มีต้นกำเนิดจากทศวรรษที่ 1960 เป็นรูปแบบข้อมูลที่เก่าแก่ที่สุดในข้างต้น มีประโยชน์กว้างขวาง สามารถใช้สำหรับการส่งข้อมูลผ่านเครือข่าย แลกเปลี่ยนข้อมูล ไฟล์กำหนดค่า การเก็บข้อมูล ฯลฯ但随着การเปลี่ยนยุค ค่อยๆถูกแทนที่ด้วยภาษา标记ใหม่

ก่อนอื่นกำหนด 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) //format

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

ผลลัพธ์

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

ผลลัพธ์

go
{UserId:120 Username:jack Age:18 Address:usa}

แต่การแยกวิเคราะห์ xml แบบดั้งเดิมมักจะต้องการสร้าง struct ใหม่ ซึ่งค่อนข้างยุ่งยาก ตอนนี้การแยกวิเคราะห์ส่วนใหญ่เป็นโครงสร้าง xml ที่เรียบง่าย หากใช้โครงสร้างที่ซับซ้อนจะทำให้ปวดหัวได้ ดังนั้นเราส่วนใหญ่จะใช้ไลบรารีโอเพนซอร์สบุคคลที่สาม etree เพื่อแยกวิเคราะห์ xml สนใจสามารถเรียนรู้เพิ่มเติมได้เอง: Go ไลบรารีที่ใช้แยกวิเคราะห์ไฟล์ xml ได้ดี etree - 掘金 (juejin.cn)

YML

ไวยากรณ์ของ YAML คล้ายกับภาษาขั้นสูงอื่นๆ และสามารถแสดงรายการ แผนที่ สเกลาร์ และรูปแบบข้อมูลอื่นๆ ได้อย่างง่าย โดยใช้การเยื้องด้วยช่องว่างและลักษณะที่ขึ้นอยู่กับรูปลักษณ์จำนวนมาก เหมาะอย่างยิ่งสำหรับการแสดงหรือแก้ไขโครงสร้างข้อมูล ไฟล์กำหนดค่าต่างๆ YML ยังมีอยู่ในหลายโปรเจกต์ในรูปแบบของไฟล์กำหนดค่า มีโครงสร้างเนื้อหาที่กระชับและชัดเจน Go ทางการไม่ได้ให้การรองรับสำหรับ YML เราจำเป็นต้องใช้แพ็กเกจบุคคลที่สาม

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

วิธีการหลัก

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

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

เตรียม 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"`
}

ไฟล์กำหนดค่า

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

ผลลัพธ์

yaml
database: oracle
url: localhost
port: 3326
username: root
password: "123456"

แต่เนื่องจาก yml เองมีไวยากรณ์การเยื้องที่เข้มงวด จึงไม่มีปัญหาเรื่องรูปแบบ 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)
}

ผลลัพธ์

{mysql 127.0.0.1 3306 root 123456}

JSON

json มักใช้ในการสื่อสารอินเทอร์เฟซสไตล์ Restful เมื่อเทียบกับ xml มีขนาดเล็กกว่า เรียนรู้ได้ง่าย ทำให้เป็นรูปแบบหลักของการแลกเปลี่ยนข้อมูลในสาขา web

ใน Go แพ็กเกจ encoding/json จัดเตรียมฟังก์ชันที่เกี่ยวข้องเพื่อดำเนินการ serialization และ deserialization ของ json ฟังก์ชันหลักที่ใช้มีดังนี้

go
func Marshal(v any) ([]byte, error) //serialize Go object เป็น json string

func Unmarshal(data []byte, v any) error //deserialize json string เป็น Go object

ก่อนอื่นกำหนด 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))
}

ผลลัพธ์

json
{ "UserId": "120", "Username": "jack", "Age": 18, "Address": "usa" }

การเปลี่ยนชื่อฟิลด์

เราสามารถบรรลุผลการเปลี่ยนชื่อผ่านแท็ก struct

go
type Person struct {
   UserId   string `json:"id"`
   Username string `json:"name"`
   Age      int    `json:"age"`
   Address  string `json:"address"`
}

ผลลัพธ์

json
{ "id": "1202", "name": "jack", "age": 19, "address": "USA" }

การเยื้อง

โดยค่าเริ่มต้น serialization จะไม่มีเยื้องใดๆ เพื่อลดการใช้พื้นที่ระหว่างการส่งข้อมูล แต่ไม่เอื้อต่อการสังเกตของมนุษย์ ในบางกรณีเราต้องการ serialize เป็นรูปแบบที่มนุษย์สามารถสังเกตได้为此只需要换一个函数

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

ผลลัพธ์

json
{
  "id": "1202",
  "name": "jack",
  "age": 19,
  "address": "USA"
}

Deserialization

ในการ deserialization ต้องระวัง หาก struct มีแท็ก json ชื่อฟิลด์จะถือตามแท็ก json เป็นหลัก มิฉะนั้นจะถือตามชื่อคุณสมบัติ 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)
}

ผลลัพธ์

go
{UserId:120 Username:jack Age:18 Address:usa}

Protocol Buffers

protocol เป็นกลไกการ serialize ข้อมูลที่มีโครงสร้างเป็นกลางทางภาษา โปรโตคอล และขยายได้ ซึ่งเปิด-source โดย Google ในปี 2008 เมื่อเทียบกับสามรูปแบบข้างต้นมีน้ำหนักเบากว่า และรวดเร็วในการ unpack และ pack ส่วนใหญ่ใช้ในสาขาการสื่อสาร RPC คำอธิบายเกี่ยวกับ Protobuf สามารถไปที่ Protobuf

ติดตั้ง dependencies

go get github.com/golang/protobuf/proto

ไฟล์ 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;
}

หลังจากสร้างไฟล์

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

ผลลัพธ์

proto buffer len:  9 bytes: [10 3 119 121 104 16 12 24 1]
name:"wyh"  age:12  gender:FE_MAIL

แต่โดยปกติเราจะไม่ serialize ด้วยตนเอง ตัวคอมไพเลอร์ protoc สามารถสร้างซอร์สโค้ดภาษาที่สอดคล้องกันตามไฟล์ proto ที่เรากำหนดไว้

Golang by www.golangdev.cn edit