การเข้ารหัส / ถอดรหัส JSON

JSON คืออะไร


JSON ย่อมาจาก JavaScript Object Notation มันคือ Standard format อย่างหนึ่งที่เป็น text และสามารถอ่านออกได้ด้วยตาเปล่า ใช้ในการสร้าง object ขึ้นมาเพื่อส่งข้อมูลระหว่าง Application หรือ Applications Program Interface (API) โดย format จะมีรูปแบบเป็น คู่ Key-Value หรือเป็นแบบ Array และสามารถนำมาใช้แทน XML format ได้

JSON เป็น format ที่ได้รับการใช้งานจาก JavaScript มาก่อน แต่ปัจจุบันมีภาษา programming หลายชนิดที่เริ่มใช้งาน JSON โดนสามารถสร้างและ แปลง format ไปมาได้

ในบทความที่แล้ว การใช้ Gorilla Websockets เราเริ่มส่งและรับข้อความผ่านซ็อกเก็ตเว็บ แต่เราจะเข้าใจข้อความที่เราได้รับได้อย่างไร กล่าวอีกนัยหนึ่งว่าเราจะถอดรหัส Jason และไปที่ค่าและ Javascript ได้อย่างไร Jason ถูกแยกวิเคราะห์เป็นวัตถุที่เราสามารถตรวจสอบคุณสมบัติของวัตถุด้วยเครื่องหมายจุด สิ่งต่าง ๆ ไม่ได้ค่อนข้างอิสระในภาษาคงที่เช่น go แต่นั่นไม่ได้หมายความว่ามันยาก

ลองมาดูกัน

สร้างไฟล์ junk.go

และ เราจะประกาศแพ็คเกจ main กล่าวอีกนัยหนึ่งเรากำลังทำให้สิ่งนี้สามารถเรียกใช้งานได้

package main


ตอนนี้ เรามาเขียนฟังก์ชัน main ที่จุดเริ่มต้นของเราไปที่ไฟล์เรียกทำงาน

func main() {
	
}

มาสร้างตัวแปรข้อความและเก็บสตริงตั้งแต่ 5 ตัวขึ้นไป โดยเฉพาะช่องอาร์เรย์ไบต์ add ข้อความเหมือนกับที่เราคาดว่าจะได้รับจากส่วนหน้าผ่านซ็อกเก็ตเว็บ เราจะกำหนดช่องชื่อข้อความและสำหรับแอตทริบิวต์ data เราจะตั้งค่าเป็น object ด้วย

recRawMsg := []byte(`{"name":"channel add",` + 
	  `"data":{"name":"Hardware Support"}}`)


ซึ่งเราสามารถนำเข้าเป็นการเข้ารหัส sosh Jason ซึ่งแพ็คเกจ Jason มีฟังก์ชัน Marshall ซึ่งจะถอดรหัส อาร์เรย์ไบต์สำหรับเรา

import (
	"encoding/json"
)



แต่สิ่งที่เราต้องการถอดรหัส ที่อยู่ติดกันใน javascript เราถอดรหัสหรือแยก Jasen เป็นของเขตข้อมูลและวิธีการที่เป็นไปได้ ดังนั้นเราจึงสามารถกำหนดโครงสร้างของการเลือกของเราและเราสามารถจัดหรือถอดรหัสอาร์เรย์ Jason byte เป็นของเรา วิธีที่คุณกำหนด struct คือโดยประเภท King ตามด้วยชื่อที่คุณต้องการใช้ ตามด้วย struct จากนั้นภายในวงเล็บปีกกา คุณจะรวมฟิลด์ที่เป็นส่วนหนึ่งของโครงสร้างที่เรากำลังสร้าง

มาสร้างโครงสร้างข้อความที่มีชื่อพร้อมชื่อฟิลด์สองชื่อ ซึ่งเรารู้ว่าจะเป็น string และ data

type Message struct {
	Name string
	Data
}

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


มีบางอย่างใน Go ที่เรียกว่าอินเทอร์เฟซ ซึ่งอินเทอร์เฟซระบุพฤติกรรมของวัตถุ ตัวอย่างเช่น สามารถสร้างอินเทอร์เฟซของลำโพงที่มีพฤติกรรมคือความสามารถในการพูด ตอนนี้ไม่ได้แสดงให้คุณเห็นว่าต้องทำอย่างไร แต่ struct สามารถมีเมธอดที่เป็นเพียงแค่ฟังก์ชันที่มี

type Speaker interface {
	Speak()
}


เข้าถึงฟิลด์ในโครงสร้าง


เราสามารถเพิ่มวิธีความเร็วให้กับข้อความของเราโดย King func จากนั้นในวงเล็บ เราจะเก็บผู้รับไว้ ซึ่งเป็นโครงสร้างที่เรากำลังเชื่อมโยงวิธีนี้และนั่นคือวิธีที่ฟังก์ชันเข้าถึงอินสแตนซ์ ของสตริง

ตัวอย่างเช่น ฟังก์ชันพูดจะพิมพ์ออกมา เราขออะไรก็ได้ที่ข้อความชื่อ Vence เป็นเหตุการณ์ กล่าวอีกนัยหนึ่งมันใช้พฤติกรรมของผู้พูดที่พูดหรือใช้วิธีพูด

func (m Message) Speak() {
	fmt.Println("I'm a " + m.Name + " event!")
}


ดังนั้นอินเทอร์เฟซจึงเป็นวิธีการทั่วไป เราจึงสามารถเขียนฟังก์ชันที่ใช้พารามิเตอร์ของ Speak ซึ่งโดยพื้นฐานแล้วจะบอกว่าคุณสามารถพูดได้

func SomeFunc(speaker Speaker){
	speaker.Speak()
}

เราจะดูเรื่องนี้ในภายหลังเมื่อเราใช้อินเทอร์เฟซจริงๆ เหตุผลที่พูดถึงตอนนี้ก็เพราะมีบางอย่างที่เรียกว่าอินเทอร์เฟซเปล่าที่เราสามารถใช้ได้

ตอนนี้อินเทอร์เฟซที่ว่างเปล่าไม่ได้กำหนดวิธีการใด ๆ ดังนั้นอาจกล่าวได้ว่าข้อมูลทุกประเภทใน go นำไปใช้ ลักษณะการทำงานของอินเทอร์เฟซที่ว่างเปล่า เราจึงสามารถระบุข้อมูลในโครงสร้างข้อความของเราเป็นอินเทอร์เฟซที่ว่างเปล่า

ดังนั้นตอนนี้จึงเป็นตัวยึดตำแหน่ง เอาล่ะ มากำจัดโค้ดอินเทอร์เฟซของ Speak กัน จากนั้นเราจะบอกว่าข้อมูลนั้นเป็นอินเทอร์เฟซที่ว่างเปล่า

กลับไปที่การถอดรหัสหรือ martialing

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

ตัวแปรข้อความไม่ใช่ประเภทตัวชี้ แต่เป็นค่า แต่เราสามารถใช้ตัวดำเนินการเครื่องหมายและส่งคืน ตัวชี้ไปยัง martialing อาจพบข้อผิดพลาดและหากเป็นเช่นนั้นจะส่งคืน ดังนั้นเราจะต้องใช้ & เพื่อเก็บไว้ในตัวแปร ถ้าข้อผิดพลาดเป็นศูนย์เราจะพิมพ์ air และส่งคืน

var recMessage Message
	err := json.Unmarshal(recRawMsg, &recMessage)
	if err != nil {
		fmt.Println(err)
		return
	}


ลองดูว่าสิ่งนี้ใช้งานได้จริงหรือไม่โดยการพิมพ์ตัวแปรข้อความไปยังคอนโซล ฉันจะใช้รูปแบบพิเศษที่พิมพ์ค่าและไวยากรณ์

fmt.Printf("%#v\n", recMessage)


มาเรียกใช้สิ่งนี้ และ ข้อเสีย ..


โค้ดทดสอบ


go run junk.go

package main

import (
	"encoding/json"
	"fmt"
)

type Message struct {
	Name string
	Data interface{}
}

func main() {
	recRawMsg := []byte(`{"name":"channel add",` +
		`"data":{"name":"Hardware Support"}}`)
	var recMessage Message
	err := json.Unmarshal(recRawMsg, &recMessage)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%#v\n", recMessage)
}


และอย่างที่คุณเห็นค่าใน Jasen ถูกจัดเก็บเป็นฟิลด์ในอินสแตนซ์ของ struct

ตอนนี้เราสามารถตรวจสอบชื่อได้ และถ้ามันเท่ากับ channel add เราก็ดำเนินการตามความเหมาะสมได้ โดยเรียกฟังก์ชัน addChannel ที่ยังไม่มีกัน

if recMessage.Name == "channel add" {
		addChannel(recMessage.Data)
	}


การส่งข้อมูลที่เราทราบคือข้อมูลช่อง ตอนนี้ มาสร้างฟังก์ชันเพิ่มแชนเนลซึ่งรับพารามิเตอร์ตัวเดียว นั่นเป็นอินเทอร์เฟซที่ว่างเปล่าสำหรับตอนนี้และส่งคืนออบเจ็กต์ช่องสัญญาณและข้อผิดพลาด เราคาดว่าข้อมูลของเราจะมีแชนเนลอยู่จริง

	func addChannel(data interface{}) (Channel, error) {

	}


มาสร้างโครงสร้างอื่นที่เรียกว่า Channel ที่มีฟิลด์ ID และฟิลด์ name ตอนนี้ในฟังก์ชัน ad channel ของเรา ให้สร้าง type channel

type Channel struct {
	Id string
	Name string
}


ตอนนี้ในฟังก์ชัน ad channel ของเรา ให้สร้างช่องตัวแปรของ type channel ประเภทข้อมูลที่แท้จริงคือ map ที่หรือ dictionary ที่มี key value pairs

ถัดไปจะต้องทำการยืนยันประเภทเพื่อรับค่าในข้อมูลในรูปแบบที่ใช้งานได้ เรามาสร้างแมปช่องตัวแปรอื่นและตั้งค่าโคลอนนั้นให้เท่ากับข้อมูล แต่ในตอนท้ายของข้อมูล เราจะทำการยืนยันแบบซึ่งโดยพื้นฐานแล้วเราบอกว่านี่คือจริงๆ ประเภทที่เฉพาะเจาะจงมากขึ้น

map ที่จะทำสิ่งนี้จะเก็บจุด จากนั้นในวงเล็บประเภทที่เรากำลังยืนยันว่านี่คือ map ที่ที่มีคีย์เป็นสตริงและมี

ค่าเป็นอินเทอร์เฟซที่ว่างเปล่า ตอนนี้เราสามารถตั้งค่าช่องชื่อช่องโดยเข้าถึงค่าที่เก็บไว้ในคีย์ชื่อและทำอย่างอื่น

พิมพ์ยืนยันสตริง เรากำลังบอกว่าค่าของคุณสมบัติชื่อเป็นสตริง โดยปกติแล้ว แนวคิดของช่องจะถูกกำหนดโดย DBI ใหม่เมื่อมีการแทรกระเบียน

แต่เนื่องจากเรากำลังจำลองขั้นตอนนั้นในตอนนี้ ให้ตั้งค่า ID เป็นสตริงเดียวตอนนี้ ไปข้างหน้า และพิมพ์ช่อง และจะส่งกลับวัตถุช่อง และ nil สำหรับ air

func addChannel(data interface{}) (Channel, error) {
  var channel Channel
  channelMap := data.(map[string]interface{})
  channel.Name = channelMap["name"].(string)
  channel.Id = "1"
  fmt.Printf("%#v\n", channel)
  return channel, nil
}


โค้ดทดสอบ

package main

import (
	"encoding/json"
	"fmt"
)

type Message struct {
	Name string
	Data interface{}
}

type Channel struct {
	Id string
	Name string
}

func main() {
	recRawMsg := []byte(`{"name":"channel add",` +
		`"data":{"name":"Hardware Support"}}`)
	var recMessage Message
	err := json.Unmarshal(recRawMsg, &recMessage)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%#v\n", recMessage)

	if recMessage.Name == "channel add" {
		addChannel(recMessage.Data)
	}
}

func addChannel(data interface{}) (Channel, error) {
	var channel Channel
	channelMap := data.(map[string]interface{})
	channel.Name = channelMap["name"].(string)
	channel.Id = "1"
	fmt.Printf("%#v\n", channel)
	return channel, nil
  }

จริง ๆ แล้วสิ่งที่ฉันเพิ่งทำไป ควรจะทำการตรวจสอบประเภทและการตรวจสอบค่าข้างต้นจริงๆ ไม่อย่างนั้นฉันอาจทำให้ panic ได้หากการยืนยันไม่ถูกต้อง แต่ฉันต้องการแสดงแก่นแท้ของ สิ่งที่ต้องทำ.

โดยเราต้องการใช้โครงสร้าง map ของ Michale Hashimoto เพื่อจัดการการถอดรหัส ของข้อมูลไปยังช่องสัญญาณซึ่งจะทำให้โค้ดเรียบง่ายและรัดกุมยิ่งขึ้น

มาติดตั้งแพ็คเกจ mapstructure ด้วยคำสั่ง GO GET ที่สำคัญที่สุดในไฟล์ของเรา

go get github.com/mitchellh/mapstructure

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

func addChannel(data interface{}) (Channel, error) {
	var channel Channel

	err := mapstructure.Decode(data, &channel)
	if err != nil {
		return channel, err
	}
	channel.Id = "1"
	fmt.Printf("%#v\n", channel)
	return channel, nil
}


กลับไปที่ที่เราเรียกว่า Ad channel และหน้าที่หลักของเรา มาเก็บที่ channel และ air ส่งค่ากลับโดย Add channel

ทีนี้ลองนึกดูว่าช่องของเราถูกเพิ่มลงในฐานข้อมูลเรียบร้อยแล้วและควรส่งกลับข้ามไป

web socket

แล้วเราจะเข้ารหัสข้อความใหม่และส่งได้อย่างไร มาสร้างตัวแปรใหม่ชื่อ send message ประเภทข้อความแล้วให้ตั้งชื่อเป็นช่องเพิ่มและตั้งค่าข้อมูลเป็นช่องของเราเพื่อเข้ารหัสสิ่งนี้ เป็นอาร์เรย์ไบต์ที่อยู่ติดกัน

เราสามารถเรียก Jason Marshall ผ่านวัตถุนั้นได้ เราต้องการเข้ารหัส Marshall ส่งคืน Jason ที่เข้ารหัสเป็นอาร์เรย์ไบต์และอาจส่งข้อผิดพลาด เช่นกัน หากอากาศเป็นศูนย์จะพิมพ์ออกมาและ Materne มิฉะนั้นเราจะพิมพ์เลขฐานสองเป็นสตริง

	if recMessage.Name == "channel add" {
		channel, err := addChannel(recMessage.Data)
		var sendMessage Message
		sendMessage.Name = "channel add"
		sendMessage.Data = channel
		sendRawMsg, err := json.Marshal(sendMessage)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(string(sendRawMsg))
	}


สร้างไฟล์ Go Modules ด้วยคำสั่ง

go mod init json


ตอนนี้ไฟล์ go.mod ถูกสร้างขึ้นสำหรับเรา เรียกใช้คำสั่งต่อไปนี้เพื่อติดตั้ง dependencies

go mod tidy


โค้ดทดสอบ

package main

import (
	"encoding/json"
	"fmt"
	"github.com/mitchellh/mapstructure"
)

type Message struct {
	Name string `json:"name"`
	Data interface{} `json:"data"`
}

type Channel struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

func main() {
	recRawMsg := []byte(`{"name":"channel add",` +
		`"data":{"name":"Hardware Support"}}`)
	var recMessage Message
	err := json.Unmarshal(recRawMsg, &recMessage)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%v\n", recMessage)

	if recMessage.Name == "channel add" {
		channel, err := addChannel(recMessage.Data)
		var sendMessage Message
		sendMessage.Name = "channel add"
		sendMessage.Data = channel
		sendRawMsg, err := json.Marshal(sendMessage)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(string(sendRawMsg))
	}
}

func addChannel(data interface{}) (Channel, error) {
	var channel Channel

	err := mapstructure.Decode(data, &channel)
	if err != nil {
		return channel, err
	}
	channel.Id = "1"
	fmt.Printf("%#v\n", channel)
	return channel, nil
}

Handling Channel Related App Messages in Go


กลับไปที่ โปรเจค การใช้ Gorilla Websockets ที่ไฟล์ main.go แก้ไขโค้ดดังนี้

package main

import (
    "fmt"
    "github.com/gorilla/websocket"
    "net/http"	
)

type Message struct {
	Name string `json:"name"`
	Data interface{} `json:"data"`
}

type Channel struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin:     func(r *http.Request) bool { return true },
}

func main() {

	http.HandleFunc("/" , handler)
	http.ListenAndServe(":4000", nil)
	
}

func handler(w http.ResponseWriter, r *http.Request) {
	socket, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
        fmt.Println(err)
		return
    }
	for {
		var inMessage Message
		if err := socket.ReadJSON(&inMessage); err != nil {
			fmt.Println(err)
			break
		}
		fmt.Printf("%#v\n", inMessage)
	}
}



ทดสอบการทำงาน go run main.go


ไปที่เว็บไซต์ https://jsbin.com/


คลิกแท็บ JavaScript

เลือก ES6 / Bable


คลิกแท็บ HTML


เขียนโค้ดดังนี้

let msg = {
  name: 'channel add',
  data: {
    name: 'Hardware Support'
  
  }
}

let ws = new WebSocket('ws://localhost:4000');

ws.onopen = () => {
  ws.send(JSON.stringify(msg));
}


คลิกแท็บ Console -> คลิกปุ่ม Run


ที่เทอมินอลของ Go จะแสดงข้อความ ที่รับเข้ามา


ที่ไฟล์ main.go แก้ไขโค้ดดังนี้

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/websocket"
	"github.com/mitchellh/mapstructure"
	"time"
)

type Message struct {
	Name string      `json:"name"`
	Data interface{} `json:"data"`
}

type Channel struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     func(r *http.Request) bool { return true },
}

func main() {

	http.HandleFunc("/", handler)
	http.ListenAndServe(":4000", nil)

}

func handler(w http.ResponseWriter, r *http.Request) {
	socket, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		fmt.Println(err)
		return
	}
	for {
		var inMessage Message
		var outMessage Message
		if err := socket.ReadJSON(&inMessage); err != nil {
			fmt.Println(err)
			break
		}
		fmt.Printf("%#v\n", inMessage)
		switch inMessage.Name {
		case "channel add":
			err := addChannel(inMessage.Data)
			if err != nil {
				outMessage = Message{"error", err}
				if err := socket.WriteJSON(outMessage); err != nil {
					fmt.Println(err)
					break
				}
			}
		case "channel subscribe":
			subscribeChannel(socket)
		}
	}
}

func addChannel(data interface{}) error {
	var channel Channel

	err := mapstructure.Decode(data, &channel)
	if err != nil {
		return err
	}
	channel.Id = "1"
	fmt.Println("added channel")
	return nil
}

func subscribeChannel(socket *websocket.Conn) {
	for {
		time.Sleep(time.Second * 1)
		message := Message{"channel add",
			Channel{"1", "Software Support"}}
		socket.WriteJSON(message)
		fmt.Println("sent new channel")
	}
}

ที่เว็บไซต์ เพิ่มโค้ดดังนี้ -> Run

let msg = {
  name: 'channel add',
  data: {
    name: 'Hardware Support'
  
  }
}

let subMsg = {
  name: 'channel subscribe'
}

let ws = new WebSocket('ws://localhost:4000');

ws.onopen = () => {
  ws.send(JSON.stringify(subMsg));
  ws.send(JSON.stringify(msg));
}
ws.onmessage = (e) => {
  console.log(e.data)
}


ที่เทอมินอลของ Go จะแสดงข้อความ 

Leave a Reply

Your email address will not be published. Required fields are marked *