การเข้ารหัสและการถอดรหัส encode ในภาษา Go
ในยุคอินเทอร์เน็ตปัจจุบัน รูปแบบข้อมูลที่เป็นอิสระจากภาษาที่ใช้บ่อยที่สุดคือ xml, Yaml, json, protobuf และ Go ก็รองรับการดำเนินการที่เกี่ยวข้องกับรูปแบบข้อมูลเหล่านี้เช่นกัน ตารางเปรียบเทียบมีดังนี้
| ชื่อ | XML | YAML | JSON | Protocol Buffers |
|---|---|---|---|---|
| โครงสร้างข้อมูล | ซับซ้อน | ค่อนข้างง่าย | ง่าย | ค่อนข้างซับซ้อน |
| วิธีการเก็บ | ข้อความ | ข้อความ | ข้อความ | ไบนารี |
| ขนาดที่เก็บ | ใหญ่ | ปานกลาง | ปานกลาง | เล็ก |
| ประสิทธิภาพการแยกวิเคราะห์ | ช้า | ปานกลาง | ปานกลาง | เร็ว |
| การรองรับภาษา | มากมาก | มาก | มากมาก | ค่อนข้างมาก |
| ความยากในการพัฒนา | ยุ่งยาก | ค่อนข้างง่าย | ง่าย | ง่าย |
| ต้นทุนการเรียนรู้ | ต่ำ | ต่ำ | ต่ำ | ต่ำ |
| ขอบเขตการใช้งาน | แลกเปลี่ยนข้อมูล | ไฟล์กำหนดค่า | แลกเปลี่ยนข้อมูล | แลกเปลี่ยนข้อมูล |
TIP
ใน Go หากต้องการทำ serialization และ deserialization กับ struct ฟิลด์จะต้องเปิดเผยภายนอก กล่าวคือตัวอักษรแรกต้องเป็นตัวพิมพ์ใหญ่
นอกจากนี้ TOML ก็เริ่มได้รับความนิยมเช่นกัน ไวยากรณ์คล้ายกับการปรับปรุง .ini สนใจสามารถไปที่ TOML: Tom's Obvious, Minimal Language เพื่อเรียนรู้เพิ่มเติม
XML
xml หรือชื่อเต็มคือ eXtensible Markup Language เป็นรูปแบบที่ใช้สำหรับเก็บข้อมูล มีต้นกำเนิดจากทศวรรษที่ 1960 เป็นรูปแบบข้อมูลที่เก่าแก่ที่สุดในข้างต้น มีประโยชน์กว้างขวาง สามารถใช้สำหรับการส่งข้อมูลผ่านเครือข่าย แลกเปลี่ยนข้อมูล ไฟล์กำหนดค่า การเก็บข้อมูล ฯลฯ但随着การเปลี่ยนยุค ค่อยๆถูกแทนที่ด้วยภาษา标记ใหม่
ก่อนอื่นกำหนด 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) //format
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))
}ผลลัพธ์
<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
}
}ผลลัพธ์
{UserId:120 Username:jack Age:18 Address:usa}แต่การแยกวิเคราะห์ xml แบบดั้งเดิมมักจะต้องการสร้าง struct ใหม่ ซึ่งค่อนข้างยุ่งยาก ตอนนี้การแยกวิเคราะห์ส่วนใหญ่เป็นโครงสร้าง xml ที่เรียบง่าย หากใช้โครงสร้างที่ซับซ้อนจะทำให้ปวดหัวได้ ดังนั้นเราส่วนใหญ่จะใช้ไลบรารีโอเพนซอร์สบุคคลที่สาม etree เพื่อแยกวิเคราะห์ xml สนใจสามารถเรียนรู้เพิ่มเติมได้เอง: Go ไลบรารีที่ใช้แยกวิเคราะห์ไฟล์ xml ได้ดี etree - 掘金 (juejin.cn)
YML
ไวยากรณ์ของ YAML คล้ายกับภาษาขั้นสูงอื่นๆ และสามารถแสดงรายการ แผนที่ สเกลาร์ และรูปแบบข้อมูลอื่นๆ ได้อย่างง่าย โดยใช้การเยื้องด้วยช่องว่างและลักษณะที่ขึ้นอยู่กับรูปลักษณ์จำนวนมาก เหมาะอย่างยิ่งสำหรับการแสดงหรือแก้ไขโครงสร้างข้อมูล ไฟล์กำหนดค่าต่างๆ YML ยังมีอยู่ในหลายโปรเจกต์ในรูปแบบของไฟล์กำหนดค่า มีโครงสร้างเนื้อหาที่กระชับและชัดเจน Go ทางการไม่ได้ให้การรองรับสำหรับ YML เราจำเป็นต้องใช้แพ็กเกจบุคคลที่สาม
go get github.com/go-yaml/yamlวิธีการหลัก
func Marshal(in interface{}) (out []byte, err error) //serialization
func Unmarshal(in []byte, out interface{}) (err error) //deserializationเตรียม struct ก่อน
type Config struct {
Database string `yaml:"database"`
Url string `yaml:"url"`
Port int `yaml:"port"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}ไฟล์กำหนดค่า
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))
}ผลลัพธ์
database: oracle
url: localhost
port: 3326
username: root
password: "123456"แต่เนื่องจาก yml เองมีไวยากรณ์การเยื้องที่เข้มงวด จึงไม่มีปัญหาเรื่องรูปแบบ 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)
}ผลลัพธ์
{mysql 127.0.0.1 3306 root 123456}JSON
json มักใช้ในการสื่อสารอินเทอร์เฟซสไตล์ Restful เมื่อเทียบกับ xml มีขนาดเล็กกว่า เรียนรู้ได้ง่าย ทำให้เป็นรูปแบบหลักของการแลกเปลี่ยนข้อมูลในสาขา web
ใน Go แพ็กเกจ encoding/json จัดเตรียมฟังก์ชันที่เกี่ยวข้องเพื่อดำเนินการ serialization และ deserialization ของ json ฟังก์ชันหลักที่ใช้มีดังนี้
func Marshal(v any) ([]byte, error) //serialize Go object เป็น json string
func Unmarshal(data []byte, v any) error //deserialize json string เป็น Go objectก่อนอื่นกำหนด 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))
}ผลลัพธ์
{ "UserId": "120", "Username": "jack", "Age": 18, "Address": "usa" }การเปลี่ยนชื่อฟิลด์
เราสามารถบรรลุผลการเปลี่ยนชื่อผ่านแท็ก struct
type Person struct {
UserId string `json:"id"`
Username string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
}ผลลัพธ์
{ "id": "1202", "name": "jack", "age": 19, "address": "USA" }การเยื้อง
โดยค่าเริ่มต้น serialization จะไม่มีเยื้องใดๆ เพื่อลดการใช้พื้นที่ระหว่างการส่งข้อมูล แต่ไม่เอื้อต่อการสังเกตของมนุษย์ ในบางกรณีเราต้องการ serialize เป็นรูปแบบที่มนุษย์สามารถสังเกตได้为此只需要换一个函数
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))
}ผลลัพธ์
{
"id": "1202",
"name": "jack",
"age": 19,
"address": "USA"
}Deserialization
ในการ deserialization ต้องระวัง หาก struct มีแท็ก json ชื่อฟิลด์จะถือตามแท็ก json เป็นหลัก มิฉะนั้นจะถือตามชื่อคุณสมบัติ 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)
}ผลลัพธ์
{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
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;
}หลังจากสร้างไฟล์
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 ที่เรากำหนดไว้
