如何处理动态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里的序列化和反序列化都做好了

  1. 针对不同的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值