您的当前位置:首页正文

用golang解析json

2024-11-29 来源:个人技术集锦


JSON (JavaScript Object Notation) 是一种简洁的数据交换格式。从语法上讲,其类似于 JavaScript 的对象和列表。最常用于 Web 后端和浏览器中运行的 JavaScript 程序之间的通信,但也用于许多其他地方。

编码

要编码 JSON 数据,我们可以使用 函数.

func Marshal(v interface{}) ([]byte, error)

给定Go数据结构Message

type Message struct {
  Name string
  Body string
  Time int64
}

并实例化Message

m := Message{"Alice", "HelloWorld", 12345678}

我们可以使用json.Marshal封装m的JSON编码版本

b, err := json.Marshal(m)

只有可以表示为有效 JSON 的数据结构才会被编码:

  • JSON 对象仅支持将字符串作为键;要编码 Go 集合类型,它必须采用 map[string]T 的形式 (其中 T 是 json 程序包支持的任何 Go 类型).
  • JSON 对象仅支持将字符串作为键;要编码 Go 集合类型,它必须采用 map[string]T 的形式 (其中 T 是 json 程序包支持的任何 Go 类型).
  • 不支持循环数据结构;它们会导致 Marshal 函数陷入无限循环.
  • 指针将被编码为其指向的值 (如果指针为 nil 则为 ‘null’).

解码

为了解码JSON数据,我们使用Unmarshal函数。

func Unmarshal(data []byte, v interface{}) error

并调用 json.Unmarshal 函数,向其传递 JSON 数据的 []byte 和指向 m 的指针

err := json.Unmarshal(b, &m)

如果 b 包含适用于 m 的有效 JSON, 则在调用 err 后将为 nil, 并且来自 b 的JSON数据将存储在结构 m 中,类似如下分配:

m = Message{
    Name: "Alice",
    Body: "Hello",
    Time: 12345678,
}

Unmarshal 函数如何识别存储解码数据的字段?对于给定的 JSON 键 “Foo”,Unmarshal 将遍历目标结构的字段以查找 (按优先顺序):

  • 标记为 “Foo” 的导出字段

  • 名为 “Foo” 的导出字段,或者

  • 名为 “FOO” 或 “FoO” 的导出字段,或 “Foo” 的其他一些不区分大小写的匹配项.

当 JSON 数据的结构与 Go 类型不完全匹配时会发生什么?

b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)

Unmarshal 函数将仅解码在目标类型中可以找到的字段。在这种情况下,将仅填充 m 的 Name 字段,而 Food 字段将被忽略。当您希望从大型 JSON Blob 中仅选择几个特定字段时,此行为特别有用。这也意味着目标结构体中任何未导出的字段都不会受到 Unmarshal 的影响.

但是,如果您事先不知道 JSON 数据的结构时怎么办?

带interface{}的通用JSON

interface{}(空接口) 类型描述了一个零方法的接口。每个 Go 类型都至少实现零个方法,因此满足空接口.

空接口用作常规容器类型:

var i interface{}
i = "a string"
i = 2011
i = 2.777

类型断言访问基础的具体类型:

r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)

或者,如果基础类型未知,则由类型开关确定类型:

switch v := i.(type) {
case int:
    fmt.Println("twice i is", v*2)
case float64:
    fmt.Println("the reciprocal of i is", 1/v)
case string:
    h := len(v) / 2
    fmt.Println("i swapped by halves is", v[h:]+v[:h])
default:
    // i isn't one of the types above
}

程序包 json 使用 map[string]interface{} 和 []interface{} 值存储任意 JSON 对象和数组;它将很乐意将任何有效的 JSON Blob 解组为一个简单的 interface{} 值。默认的具体 Go 类型是:

  • bool 对照为 JSON 布尔值,
  • float64 对照为 JSON 数值,
  • string 对照为 JSON 字符串,以及
  • nil 对照为 JSON 空.

解码任意数据

考虑一下存储在变量 b 中的 JSON 数据:

b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

在不知道此数据结构的情况下,我们可以使用 Unmarshal 将其解码为 interface{} 值:

var f interface{}
err := json.Unmarshal(b, &f)

此时 f 中的 Go 值将是一个集合,其键为字符串,其值本身存储为空接口值:

f = map[string]interface{}{
    "Name": "Wednesday",
    "Age":  6,
    "Parents": []interface{}{
        "Gomez",
        "Morticia",
    },
}

要访问此数据我们可以使用类型断言来访问 fmap[string]interface{}:

m := f.(map[string]interface{})

然后我们可以使用 range 语句遍历集合,并使用类型开关将其值作为其具体类型来访问:

for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case float64:
        fmt.Println(k, "is float64", vv)
    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don't know how to handle")
    }
}

这样您就可以使用未知的 JSON 数据,同时仍然享有类型安全的好处.

引用类型

type FamilyMember struct {
    Name    string
    Age     int
    Parents []string
}

    var m FamilyMember
    err := json.Unmarshal(b, &m)

将数据解封为 FamilyMember 值可以按预期工作,但是如果仔细观察,我们可以看到发生了一件了不起的事情。通过 var 语句,我们分配了一个 FamilyMember 结构,然后将指向该值的指针提供给 Unmarshal 函数,但是那时 Parents 字段是一个 nil 切片值。为了填充 Parents 字段,Unmarshal 函数在幕后分配了一个新切片。这就是 Unmarshal 函数如何与支持的引用类型 (指针,切片和集合) 一起使用的典型方法.

考虑解封到此数据结构中:

type Foo struct {
    Bar *Bar
}

如果 JSON 对象中有一个 Bar 字段,则 Unmarshal 将分配一个新的 Bar 并填充它。如果不是,Bar 将保留为 nil 指针.

由此产生一种有用的模式:如果您的应用程序接收一些不同的消息类型,则可以定义 “接收器” 结构,例如

type IncomingMessage struct {
    Cmd *Command
    Msg *Message
}

发送方可以根据他们想要传达的消息类型来填充 Cmd 字段或顶级 JSON 对象的 Msg 字段。当将 JSON 解码为 IncomingMessage 结构时,Unmarshal 仅将分配存在于 JSON 数据中的数据结构。要知道要处理哪些消息,程序员只需测试 Cmd 或 Msg 不是 nil.

编码器和解码器

程序包 json 提供 Decoder 和 Encoder 类型来支持读取和写入 JSON 数据流的通用操作. NewDecoder 和 NewEncoder 函数包装了 io.Reader 和 io.Writer 接口类型.

这是一个示例程序,该程序从标准输入读取一系列 JSON 对象,从每个对象中删除了除 Name 字段之外的所有字段,然后将这些对象写入标准输出:

package main

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

func main() {
    dec := json.NewDecoder(os.Stdin)
    enc := json.NewEncoder(os.Stdout)
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Name" {
                delete(v, k)
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}

由于读取器和写入器无处不在,因此这里的 EncoderDecoder 类型可以在广泛的场景中使用,例如对 HTTP 连接,WebSocket 或文件的读写.

显示全文