zoukankan      html  css  js  c++  java
  • 如何处理动态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
  • 相关阅读:
    UIControl IOS控件编程 及UITextField的讲解
    ViewPager实现页面切换
    HDU 3788 和九度OJ 1006测试数据是不一样的
    的基本原理的面向对象的--------单个程序员必须查看
    hdu1796 How many integers can you find
    Android数据加载和Json解析——蓝本
    设计管理员表;webservice用于网络安全的高端内提供服务的
    how tomcat works 札记(两)----------一个简单的servlet集装箱
    新秀学习Hibernate——一个简单的例子
    在cocos2d-x在CCTableView使用控制
  • 原文地址:https://www.cnblogs.com/linyihai/p/10802459.html
Copyright © 2011-2022 走看看