如何處理動態JSON in Go


假如要設計一個統計的json解析模塊,json格式為

{
    "type": "用來識別不同的json數據",
    "msg": "嵌套的實際數據"
}

代碼

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Envelope struct {
	Type string
	Msg  interface{} // 接受任意的類型
}

type Sound struct {
	Description string
	Authority   string
}

type Cowbell struct {
	More bool
}

func main() {
	s := Envelope{
		Type: "sound",
		Msg: Sound{
			Description: "dynamite",
			Authority:   "the Bruce Dickinson",
		},
	}
	buf, err := json.Marshal(s)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)

	c := Envelope{
		Type: "cowbell",
		Msg: Cowbell{
			More: true,
		},
	}
	buf, err = json.Marshal(c)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)
}

我們定義Msg類型為interface{},用來接受任意的類型。接下來試着解析msg中的字段

const input = `
{
	"type": "sound",
	"msg": {
		"description": "dynamite",
		"authority": "the Bruce Dickinson"
	}
}
`
var env Envelope
if err := json.Unmarshal([]byte(input), &env); err != nil {
	log.Fatal(err)
}
// for the love of Gopher DO NOT DO THIS
var desc string = env.Msg.(map[string]interface{})["description"].(string)
fmt.Println(desc)

有更好的寫法,使用*json.RawMessage, 將msg字段延遲解析

type Envelope {
	Type string
	Msg  *json.RawMessage
}

結合interface{}和*json.RawMessage的完整例子

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

const input = `
{
	"type": "sound",
	"msg": {
		"description": "dynamite",
		"authority": "the Bruce Dickinson"
	}
}
`

type Envelope struct {
	Type string
	Msg  interface{}
}

type Sound struct {
	Description string
	Authority   string
}

func main() {
	var msg json.RawMessage
	env := Envelope{
		Msg: &msg,
	}
	if err := json.Unmarshal([]byte(input), &env); err != nil {
		log.Fatal(err)
	}
	switch env.Type {
	case "sound":
		var s Sound
		if err := json.Unmarshal(msg, &s); err != nil {
			log.Fatal(err)
		}
		var desc string = s.Description
		fmt.Println(desc)
	default:
		log.Fatalf("unknown message type: %q", env.Type)
	}
}

第一部分結束了,接下來還有來個地方可以提升

  1. 將定義的json數據中的type字段抽出來,單獨定義成一個枚舉常量。需要使用github.com/campoy/jsonenums
//go:generate jsonenums -type=Kind

type Kind int

const (
	sound Kind = iota
	cowbell
)

定義完上述內容后,執行命令

jsonenums -type=Pill

這個模塊會自動生成一個*_jsonenums.go的文件,里面定義好了

func (t T) MarshalJSON() ([]byte, error)
func (t *T) UnmarshalJSON([]byte) error

這樣,就幫我們把自定義的Kind和json type里的序列化和反序列化都做好了
2. 針對不同的json type字段,可以定義一個方法來返回不同的msg struct

var kindHandlers = map[Kind]func() interface{}{
	sound:   func() interface{} { return &SoundMsg{} },
	cowbell: func() interface{} { return &CowbellMsg{} },
}
  1. 結合1,2把之前代碼的switch塊去掉
    完整代碼:
type App struct {
	// whatever your application state is
}

// Action is something that can operate on the application.
type Action interface {
	Run(app *App) error
}

type CowbellMsg struct {
	// ...
}

func (m *CowbellMsg) Run(app *App) error {
	// ...
}

type SoundMsg struct {
	// ...
}

func (m *SoundMsg) Run(app *App) error {
	// ...
}

var kindHandlers = map[Kind]func() Action{
	sound:   func() Action { return &SoundMsg{} },
	cowbell: func() Action { return &CowbellMsg{} },
}

func main() {
	app := &App{
		// ...
	}

	// process an incoming message
	var raw json.RawMessage
	env := Envelope{
		Msg: &raw,
	}
	if err := json.Unmarshal([]byte(input), &env); err != nil {
		log.Fatal(err)
	}
	msg := kindHandlers[env.Type]()
	if err := json.Unmarshal(raw, msg); err != nil {
		log.Fatal(err)
	}
	if err := msg.Run(app); err != nil {
		// ...
	}
}

接下來是另外一種設想,加入定義的json字段都放在最外層,即沒有了嵌套的msg字段

{
    "type": "用來識別不同的json數據",
    ...
}

那需要umarshal兩次json,第一次比對type字段,針對不同的type字段來unmarsh一次

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

const input = `
{
	"type": "sound",
	"description": "dynamite",
	"authority": "the Bruce Dickinson"
}
`

type Envelope struct {
	Type string
}

type Sound struct {
	Description string
	Authority   string
}

func main() {
	var env Envelope
	buf := []byte(input)
	if err := json.Unmarshal(buf, &env); err != nil {
		log.Fatal(err)
	}
	switch env.Type {
	case "sound":
		var s struct {
			Envelope
			Sound
		}
		if err := json.Unmarshal(buf, &s); err != nil {
			log.Fatal(err)
		}
		var desc string = s.Description
		fmt.Println(desc)
	default:
		log.Fatalf("unknown message type: %q", env.Type)
	}
}

本文是下述博客的翻譯和整理,僅供參考

  1. Dynamic JSON in Go
  2. Go JSON unmarshaling based on an enumerated field value


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM