如何处理动态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
", buf)

	c := Envelope{
		Type: "cowbell",
		Msg: Cowbell{
			More: true,
		},
	}
	buf, err = json.Marshal(c)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s
", 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
原文地址:https://www.cnblogs.com/linyihai/p/10802459.html