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 ขณะการอ่าน ลดการใช้หน่วยความจำและเพิ่มประสิทธิภาพในการประมวลผล ซึ่งมีประโยชน์มากโดยเฉพาะสำหรับการจัดการการถ่ายโอนข้อมูลผ่านเครือข่ายหรือไฟล์ขนาดใหญ่