1. ภาพรวมของไลบรารีมาตรฐาน encoding/json
โปรแกรมภาษา Go มีไลบรารีที่มีประสิทธิภาพชื่อว่า encoding/json
ซึ่งใช้สำหรับการจัดการรูปแบบข้อมูล JSON ได้อย่างมีประสิทธิภาพ ด้วยไลบรารีนี้ คุณสามารถแปลงประเภทข้อมูลของ Go เป็นรูปแบบ JSON (การ serialize) หรือแปลงข้อมูล JSON กลับเป็นประเภทข้อมูลของ Go (การ deserialize) ได้อย่างง่ายดาย ไลบรารีนี้มีความสามารถมากมาย เช่น การเข้ารหัส การถอดรหัส การบันทึกการตอนไปวางบิต และรองรับการตรวจสอบรูปแบบ JSON ที่กำหนดเอง
ประเภทข้อมูลและฟังก์ชันที่สำคัญที่สุดในไลบรารีนี้รวมถึง:
-
Marshal
และMarshalIndent
: ใช้ในการเข้ารหัสประเภทข้อมูลของ Go เป็นสตริง JSON -
Unmarshal
: ใช้ในการถอดรหัสสตริง JSON เป็นประเภทข้อมูลของ Go -
Encoder
และDecoder
: ใช้สำหรับการตอนไปวางบิตของข้อมูล JSON -
Valid
: ใช้ในการตรวจสอบว่าสตริงที่ได้รับมีรูปแบบ JSON ที่ถูกต้องหรือไม่
เราจะเรียนรู้การใช้ฟังก์ชันและประเภทเหล่านี้โดยเฉพาะในบทต่อไป
2. การเข้ารหัสโครงสร้างข้อมูลของ Go เป็น JSON
2.1 การใช้ json.Marshal
json.Marshal
เป็นฟังก์ชันที่ใช้ในการเข้ารหัสประเภทข้อมูลของ Go เป็นสตริง JSON มันรับประเภทข้อมูลจากภาษา Go เป็นอินพุต แปลงเป็นรูปแบบ JSON และคืนสตริงไบต์พร้อมกับค่าผิดพลาดที่เป็นไปได้
นี่คือตัวอย่างที่ง่ายที่แสดงวิธีการแปลงโครงสร้างของ Go เป็นสตริง JSON:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
person := Person{"Alice", 30}
jsonData, err := json.Marshal(person)
if err != nil {
log.Fatalf("การเข้ารหัส JSON ล้มเหลว: %s", err)
}
fmt.Println(string(jsonData)) // ผลลัพธ์: {"name":"Alice","age":30}
}
นอกจากสตรัคต์ json.Marshal
สามารถเข้ารหัสประเภทข้อมูลอื่น ๆ เช่น แผนที่และสไลด์ได้เช่นกัน ตัวอย่างด้านล่างแสดงให้เห็นถึงการใช้ map[string]interface{}
และ slice
:
// แปลงแผนที่เป็น JSON
myMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
jsonData, err := json.Marshal(myMap)
// ... การจัดการข้อผิดพลาดและผลลัพธ์ไม่ได้แสดง ...
// แปลงสไลด์เป็น JSON
mySlice := []string{"Apple", "Banana", "Cherry"}
jsonData, err := json.Marshal(mySlice)
// ... การจัดการข้อผิดพลาดและผลลัพธ์ไม่ได้แสดง ...
2.2 แท็กสตรัคต์
ใน Go แท็กสตรัคต์ถูกใช้เพื่อให้ข้อมูลมีเมตาดาต้าสำหรับฟิลด์ของสตรักจุก, ควบคุมพฤติกรรมในการเข้ารหัส JSON รูปแบบที่ใช้บ่อยที่สุดรวมถึงการเปลี่ยนชื่อฟิลด์, การไม่สนใจฟิลด์, และการเข้ารหัสที่เป็นเงื่อนไข
เช่น คุณสามารถใช้แท็ก json:"<name>"
เพื่อระบุชื่อของฟิลด์ JSON ได้เช่น:
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"` // การเพิ่มแท็ก "-" หน้าฟิลด์ Tag บอกให้ `json.Marshal` ไม่สนใจฟิลด์นี้
}
ในตัวอย่างด้านบน แท็ก json:"-"
ข้างหน้าฟิลด์ Tag
บอก json.Marshal
ให้ไม่สนใจฟิลด์นี้ ตัวเลือก omitempty
สำหรับฟิลด์ Description
บ่งบอกว่าหากฟิลด์ว่างเปล่า (ค่าศูนย์ เช่น สตริงเปล่า) ก็จะไม่ถูกรวมอยู่ใน JSON ที่แข่งเข่าไป
นี่คือตัวอย่างละเอียดการใช้แท็กสตรัคต์:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"`
}
func main() {
animal := Animal{
SpeciesName: "African Elephant",
Description: "สัตว์เลี้ยงขนาดใหญ่ที่มีงวงและเขา",
Tag: "endangered", // ฟิลด์นี้จะไม่ถูกเข้ารหัสไปยัง JSON
}
jsonData, err := json.Marshal(animal)
if err != nil {
log.Fatalf("การเข้ารหัส JSON ล้มเหลว: %s", err)
}
fmt.Println(string(jsonData)) // ผลลัพธ์: {"species":"African Elephant","desc":"สัตว์เลี้ยงขนาดใหญ่ที่มีงวงและเขา"}
}
อย่างนี้คุณสามารถให้การจัดระเบียบข้อมูลที่ชัดเจนในขณะที่ควบคุมรูปแบบ JSON ได้อย่างยืดหยุ่นในการจัดการความต้องการของการเข้ารหัสได้แต่ละอย่าง###
3. การเข้ารหัส JSON เป็นโครงสร้างข้อมูลของ Go
3.1 การใช้ json.Unmarshal
ฟังก์ชัน json.Unmarshal
ช่วยให้เราแปลง JSON strings เป็นโครงสร้างข้อมูลของ Go เช่น structs, maps, เป็นต้น หากต้องการใช้ json.Unmarshal
เราต้องกำหนดโครงสร้างข้อมูลของ Go ที่ตรงกับข้อมูล JSON ก่อน
ถ้าเรามีข้อมูล JSON ดังต่อไปนี้:
{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}
เพื่อแปลงข้อมูลนี้เป็น struct ของ Go เราจะต้องกำหนด struct ที่ตรงกัน:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails"`
}
ต่อมาเราสามารถใช้ json.Unmarshal
สำหรับการทำ deserialization:
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
return
}
fmt.Printf("User: %+v\n", user)
}
ในตัวอย่างข้างต้น เราใช้ tag เช่น json:"name"
เพื่อแจ้งให้ฟังก์ชัน json.Unmarshal
เรียกให้เราเกี่ยวกับการ map ของ JSON fields ไปยัง struct fields นั่นเอง
3.2 การแยกการ Parsing แบบไดนามิก
บางครั้งโครงสร้างของ JSON ที่เราต้องการแยกอาจไม่มีทรัพยากรล่วงหน้าหรือโครงสร้างของข้อมูล JSON อาจเปลี่ยนแปลงได้ต่อเนื่อง ในกรณีเช่นนี้ เราสามารถใช้ interface{}
หรือ json.RawMessage
สำหรับการแยกการ
การใช้ interface{}
ช่วยให้เราสามารถแยกโดยไม่ต้องทราบโครงสร้างของ JSON:
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engineer"
}
}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
// การใช้ assert ให้แน่ใจว่า type ตรงกับก่อนใช้
name := result["name"].(string)
fmt.Println("Name:", name)
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // หมายเหตุ: ตัวเลขใน interface{} จะถูกจัดเป็น float64
fmt.Println("Age:", age)
}
การใช้ json.RawMessage
ช่วยให้เราสามารถเก็บ JSON ต้นฉบับไว้ในขณะที่แยกการแบบเลือกได้:
type UserDynamic struct {
Name string `json:"name"`
Details json.RawMessage `json:"details"`
}
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Engineer"
}
}`
var user UserDynamic
json.Unmarshal([]byte(jsonData), &user)
var details map[string]interface{}
json.Unmarshal(user.Details, &details)
fmt.Println("Name:", user.Name)
fmt.Println("Age:", details["age"])
fmt.Println("Job:", details["job"])
}
วิธีนี้เป็นประโยชน์สำหรับการจัดการโครงสร้าง JSON ที่บาง field อาจมีประเภทข้อมูลที่แตกต่างกัน และช่วยให้การจัดการข้อมูลอย่างยืดหยุ่นได้
4 การจัดการโครงสร้างที่ซับซ้อนและอาร์เรย์
4.1 วัตถุ JSON ที่ซับซ้อน
ข้อมูล JSON ที่พบบ่อยไม่ได้เป็นแบบแบน แต่มีโครงสร้างที่ซับซ้อน ใน Go เราสามารถจัดการกรณีเช่นนี้โดยกำหนด structs ที่ซับซ้อน
เราสมมุติว่าเรามี JSON ที่มีโครงสร้างที่ซับซ้อนดังต่อไปนี้:
{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}
เราสามารถกำหนด struct ของ Go ได้ดังนี้:
type ContactInfo struct {
Email string `json:"email"`
Address string `json:"address"`
}
type UserWithContact struct {
Name string `json:"name"`
Contact ContactInfo `json:"contact"`
}
การดำเนินการ deserialization เหมือนกับโครงสร้างที่ไม่ซับซ้อน:
func main() {
jsonData := `{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}`
var user UserWithContact
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("Error unmarshalling JSON:", err)
}
fmt.Printf("%+v\n", user)
}
4.2 อาร์เรย์ JSON
ใน JSON อาร์เรย์เป็นโครงสร้างข้อมูลที่พบบ่อย ใน Go สามารถแปลงเป็น slices
พิจารณา JSON อาร์เรย์ต่อไปนี้:
[
{"name": "เดฟ", "age": 34},
{"name": "อีฟ", "age": 28}
]
ใน Go เรากำหนด struct และ slice ที่สอดคล้องกัน ดังนี้:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := `[
{"name": "เดฟ", "age": 34},
{"name": "อีฟ", "age": 28}
]`
var people []Person
json.Unmarshal([]byte(jsonData), &people)
for _, person := range people {
fmt.Printf("%+v\n", person)
}
}
โดยทำแบบนี้ เราสามารถแปลงข้อมูลแต่ละองค์ใน JSON อาร์เรย์เป็น slice ของ Go structs เพื่อประมวลผลและเข้าถึงต่อไป
5 การจัดการข้อผิดพลาด
เมื่อจัดการกับข้อมูล JSON ไม่ว่าจะเป็นการซีเรียลไลเซชัน (แปลงข้อมูลโครงสร้างเป็นรูปแบบ JSON) หรือการแสดงไลเซชัน (แปลง JSON กลับเป็นข้อมูลโครงสร้าง) ข้อผิดพลาดอาจเกิดขึ้น เราจะพูดถึงข้อผิดพลาดที่พบบ่อย และวิธีการจัดการกับมันต่อไป
5.1 การจัดการข้อผิดพลาดขณะการซีเรียลไลเซชัน
ข้อผิดพลาดขณะการซีเรียลไลเซชันแตกต่างกัน โดยสะพระแรมข้อมูลโครงสร้างหรืออย่างอื่น เป็นรูปแบบ JSON ตัวอย่างเช่น หากพยายามที่จะซีเรียลไลเซชัน struct ที่ประกอบด้วยฟิลด์ที่ผิดกฎหมาย (เช่น ชนิดช่องในหรือฟังก์ชันที่ไม่สามารถแทนด้วย JSON) json.Marshal
จะส่งคืนข้อผิดพลาด
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Age int
// สมมติว่ามีฟิลด์ที่ไม่สามารถซีเรียลไลเซชันได้ที่นี่
// Data chan struct{} // ช่องไม่สามารถแทนด้วย JSON
}
func main() {
u := User{
Name: "อลิซ",
Age: 30,
// Data: make(chan struct{}),
}
bytes, err := json.Marshal(u)
if err != nil {
log.Fatalf("การซีเรียลไลเซชัน JSON ล้มเหลว: %v", err)
}
fmt.Println(string(bytes))
}
ในตัวอย่างด้านบน เราทำการ comment ส่วน Data
เจตแต่ถูก comment ไว้ หากไม่ comment การซีเรียลไลเซชันจะล้มเหลว และโปรแกรมจะเขียนบันทึกข้อผิดพลาดและสิ้นสุดการทำงาน การจัดการข้อผิดพลาดในทางทั่วไปนั้นเริ่มด้วยการตรวจสอบข้อผิดพลาดและใช้กลยุทธ์การจัดการข้อผิดพลาดที่สอดคล้อง (เช่น บันทึกข้อผิดพลาด ส่งคืนข้อมูลเริ่มต้น ฯลฯ)
5.2 การจัดการข้อผิดพลาดขณะการแสดงไลเซชัน
ข้อผิดพลาดขณะการแสดงไลเซชันอาจเกิดขึ้นขณะแปลงข้อมูล JSON กลับไปเป็น struct ของ Go หรือชนิดข้อมูลอื่น ๆ ตัวอย่างเช่น หากรูปแบบข้อมูล JSON ไม่ถูกต้องหรือไม่สามารถให้สอดคล้องกับชนิดเป้าหมาย json.Unmarshal
จะส่งข้อผิดพลาดกลับ
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data = []byte(`{"name":"อลิซ","age":"unknown"}`) // "age" ควรเป็นจำนวนเต็ม แต่ที่นี่ให้สตริง
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("การแสดงไลเซชัน JSON ล้มเหลว: %v", err)
}
fmt.Printf("%+v\n", u)
}
ในตัวอย่างโค้ดนี้ เราให้ชนิดข้อมูลผิดตามในฟิลด์ age
(เป็นสตริงแทนของจำนวนเต็มที่คาดหวัง) ทำให้ json.Unmarshal
ส่งข้อผิดพลาด ดังนั้นเราจะต้องจัดการสถานการณ์นี้อย่างเหมาะสม วิธีการที่พบบ่อยคือบันทึกข้อความข้อผิดพลาดและมีโอกาสส่งคืนวัตถุเปล่า ค่าเริ่มต้น หรือข้อความข้อผิดพลาดตามสถานการณ์
6.1 การกำหนดรูปแบบการแปลงข้อมูลและการยัดและแยกข้อมูล
โดยค่าเริ่มต้นแพ็คเกจ encoding/json
ใน Go ทำการซีเรียไล่และไซเรียไล้ JSON ผ่านการสะท้อสะเท้อ อย่างไรก็ตามเราสามารถกำหนดการกระทำเหล่านี้โดยการปรับใช้อินเตอร์เฟซ json.Marshaler
และ json.Unmarshaler
ได้
import (
"encoding/json"
"fmt"
)
type Color struct {
Red uint8
Green uint8
Blue uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02x%02x%02x\"", c.Red, c.Green, c.Blue)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
_, err := fmt.Sscanf(string(data), "\"#%02x%02x%02x\"", &c.Red, &c.Green, &c.Blue)
return err
}
func main() {
c := Color{Red: 255, Green: 99, Blue: 71}
jsonColor, _ := json.Marshal(c)
fmt.Println(string(jsonColor))
var newColor Color
json.Unmarshal(jsonColor, &newColor)
fmt.Println(newColor)
}
ที่นี่เราได้กำหนดประเภท Color
และนำเอาวิธี MarshalJSON
และ UnmarshalJSON
มาใช้เพื่อแปลงสี RGB เป็นข้อความสิบหกตัวฐานและกลับมาเป็นสี RGB อีกครั้ง
6.2 ตัวเอกเดอร์และตัวถอยโจมตัวถอยโจม
เมื่อมีการจัดการข้อมูล JSON ขนาดใหญ่ การใช้ json.Marshal
และ json.Unmarshal
โดยตรงอาจทำให้การใช้หน่วยความจำมากเกินไปหรือปฏิบัติการอินพุท/เอาท์พุทที่ไม่เป็นประสิทธิภาพ ด้วยเหตุนี้ แพ็คเกจ encoding/json
ใน Go จึงจัดหาตัวเอกเดอร์และตัวถอยโจมเพื่อความสามารถในการประมวลผลข้อมูล JSON แบบสตรีมมิง
6.2.1 การใช้ json.Encoder
json.Encoder
สามารถเขียนข้อมูล JSON โดยตรงไปยังวัตถุใดวัตถุหนึ่งที่ประมวลผลอินเตอร์เฟซ io.Writer ซึ่งหมายความว่าคุณสามารถเข้ารหัสข้อมูล JSON โดยตรงไปยังไฟล์ เชื่อมต่อเครือข่าย ฯลฯ
import (
"encoding/json"
"os"
)
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(users); err != nil {
log.Fatalf("Encoding error: %v", err)
}
}
6.2.2 การใช้ json.Decoder
json.Decoder
สามารถอ่านข้อมูล JSON โดยตรงจากวัตถุใดวัตถุหนึ่งที่ประมวลผลอินเตอร์เฟซ io.Reader และค้นหาและแยกข้อมูล JSON ตัววัตถุและอาร์เรย์
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Open("users.json")
defer file.Close()
var users []User
decoder := json.NewDecoder(file)
if err := decoder.Decode(&users); err != nil {
log.Fatalf("Decoding error: %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
โดยการประมวลผลข้อมูลด้วยตัวเอกเดอร์และตัวถอยโจม คุณสามารถดำเนินการประมวลผล JSON ขณะการอ่าน ลดการใช้หน่วยความจำและเพิ่มประสิทธิภาพในการประมวลผล ซึ่งมีประโยชน์มากโดยเฉพาะสำหรับการจัดการการถ่ายโอนข้อมูลผ่านเครือข่ายหรือไฟล์ขนาดใหญ่