Go语言学习#11-如何解析json字符串


=Start=

缘由:

现在的json格式用的非常之多,比如:配置文件,接口之间的数据传递。所以,用Go 语言来对json字符串进行读取、解析、在各类型之间进行转换是一个非常常见的问题,这里简单整理总结一下,方便以后用到的时候参考。

正文:

参考解答:

Go 语言在内置的”encoding/json”库中提供了Marshal和Unmarshal函数以支持(已知结构的)struct结构体和json字符串之间的转换:

// 将 struct结构体 转成 json格式串
func Marshal(v interface{}) ([]byte, error)
// Marshal returns the JSON encoding of v.

// 将 json格式串 转成 struct结构体
func Unmarshal(data []byte, v interface{}) error
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
1、将固定格式的json字符串转换成struct结构体

来一个实际点的例子就是:通过接口 http://ipinfo.io/json 获取自己网络出口公网IP地址的相关信息如下:

{
  "ip": "115.21.14.38",
  "city": "Xinghuo",
  "region": "Beijing",
  "country": "CN",
  "loc": "58.2500,36.1670",
  "org": "AS165256 CN CARE NETWORK LTD"
}

然后你想从上面这段json字符串中解析出IP地址和地区region出来,因为返回内容的格式都是固定的,所以我们可以先对着内容定义一个结构体IpInfo出来,借助Unmarshal函数将json字符串解析到一个IpInfo结构体变量中,然后进行下一步的处理:

package main

import (
    "encoding/json"
    "log"
)

// 在 tag 中定义的名称不需要首字母大写,且对应的 json 串中字段名称仍然大小写不敏感
type IpInfo struct {
    Ip      string `json:"ip"`
    City    string `json:"city"`
    Region  string `json:"region"`
    Country string `json:"country"`
    Loc     string `json:"loc"`
    Org     string `json:"org"`
}

func main() {
    jsonStr := `{
  "ip": "115.21.14.38",
  "city": "Xinghuo",
  "region": "Beijing",
  "country": "CN",
  "loc": "58.2500,36.1670",
  "org": "AS165256 CN CARE NETWORK LTD"
}`
    log.Printf("jsonStr:\n%s\n", jsonStr)

    var ipInfo IpInfo
    json.Unmarshal([]byte(jsonStr), &ipInfo)
    log.Printf("[ipInfo]value: %v\ntype: %T\n", ipInfo, ipInfo) // type: main.IpInfo
    log.Println("ipInfo.Ip:", ipInfo.Ip)
    // log.Println("ipInfo.ip:", ipInfo.ip) // ipInfo.ip undefined
}
2.将struct结构体变量转换成json字符串的形式

这里列出两个方法:

  • json.NewEncoder(b).Encode(struct_var)
  • b2, err := json.Marshal(struct_var)
package main

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

type User struct {
    Name    string
    Age     uint32
    Balance uint64
    test    int // 在 struct 中 key 是小写开头的项为「不可导出项」,因为只有大写的才是可对外提供访问的
}

// 在 tag 中定义的名称不需要首字母大写,且对应的 json 串中字段名称仍然大小写不敏感
type Employee struct {
    Name   string `json:"empname"` // tag标签
    Number int    `json:"empid"`
}

func main() {
    u := User{Name: "ixyzero.com", Balance: 8}
    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(u) // 方法一

    log.Printf("[u]value: %v\ntype: %T\n\n", u, u) // type: main.User (可以看到test字段的内容)
    log.Printf("[b]value: %v\ntype: %T\n", b, b)   // type: *bytes.Buffer

    user := User{Name: "ixyzero", Age: 20, Balance: 5, test: 111}
    b2, err := json.Marshal(user) // 方法二
    if err != nil {
        log.Println(err)
    }
    log.Printf("[b2]value: %v\ntype: %T\n", b2, b2) // type: []uint8

    str2 := string(b2)
    log.Printf("[str2]value: %v\ntype: %T\n", str2, str2) // type: string

    emp := &Employee{Name: "Rocky", Number: 5454}
    e, err := json.Marshal(emp)
    if err != nil {
        log.Println(err)
    }
    str2 = string(e)
    log.Printf("[str2]value: %v\ntype: %T\n", str2, str2) // type: string
}

/*
2018/10/27 14:52:02 [u]value: {ixyzero.com 0 8 0}
type: main.User

2018/10/27 14:52:02 [b]value: {"Name":"ixyzero.com","Age":0,"Balance":8}

type: *bytes.Buffer
2018/10/27 14:52:02 [b2]value: [123 34 78 97 109 101 34 58 34 105 120 121 122 101 114 111 34 44 34 65 103 101 34 58 50 48 44 34 66 97 108 97 110 99 101 34 58 53 125]
type: []uint8
2018/10/27 14:52:02 [str2]value: {"Name":"ixyzero","Age":20,"Balance":5}
type: string
2018/10/27 14:52:02 [str2]value: {"empname":"Rocky","empid":5454}
type: string
*/
3.嵌套json字符串的解析

上面2种讲的其实都是格式比较简单的json字符串的处理,但实际场景中可能嵌套格式的json字符串会占多数,比如:

{
  "args": {},
  "data": "{\"title\":\"a breakfast\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept-Encoding": "gzip",
    "Connection": "close",
    "Content-Length": "23",
    "Content-Type": "application/json",
    "Cookie": "id=anyone",
    "Host": "httpbin.org",
    "User-Agent": "iPhone X"
  },
  "json": {
    "title": "a breakfast"
  },
  "origin": "137.21.83.79",
  "url": "https://httpbin.org/post"
}

针对这种较为复杂的嵌套格式json字符串的处理,方法简单的来说有3种:

  • 一、先定义好(对应格式)嵌套结构的struct结构体;
  • 二、先使用 interface{} 接收,实际使用的时候再处理一遍;
  • 三、直接使用外部包,比如:gjson或者fastjson

这里为了演示方便起见,先用gjson试试,其中nested.json文件的内容是上面的那段json字符串。

package main

import (
    "encoding/json"
    "fmt"
    "github.com/tidwall/gjson"
    "io/ioutil"
    "os"
)

func typeof(v interface{}) string {
    switch v.(type) {
    case int:
        return "int"
    case float64:
        return "float64"
    case map[string]interface{}:
        return "map[string]interface {}"
    //... etc
    default:
        return "unknown"
    }
}

func main() {

    file, e := ioutil.ReadFile("./nested.json")
    if e != nil {
        fmt.Printf("File error: %v\n", e)
        os.Exit(1)
    }
    myJson := string(file)
    m, ok := gjson.Parse(myJson).Value().(map[string]interface{})
    if !ok {
        fmt.Println("Error")
    }

    for key, item := range m {
        value_type := typeof(item)
        if value_type == "map[string]interface {}" {
            fmt.Printf("key: %v(%T), item: %v, type: %T\n", key, key, item, item)
            itemMap := item.(map[string]interface{}) // 要先进行一次类型转换(暂时还没搞清楚具体原因)
            fmt.Printf("key: %v(%T), itemMap: %v, type: %T\n", key, key, itemMap, itemMap)
            // for k, v := range item { // cannot range over item (type interface {})
            for k, v := range itemMap {
                fmt.Printf("\tkey: %v(%T), value: %v, type: %T\n", k, k, v, v)
            }
        } else {
            fmt.Printf("key: %v(%T), value: %v, type: %T\n", key, key, item, item)
        }
    }
    fmt.Println()

    jsonBytes, err := json.Marshal(m)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(string(jsonBytes))
}

 

参考链接:

=END=

, ,

《 “Go语言学习#11-如何解析json字符串” 》 有 6 条评论

  1. Go 语言操作 JSON 的几种方法
    https://morning.work/page/go/json.html
    `
    # 标准库的 json 模块
    Go 语言标准库 encoding/json 提供了操作 JSON 的方法,一般可以使用 json.Marshal 和 json.Unmarshal 来序列化和解析 JSON 字符串:

    # 更加灵活和更好性能的 jsoniter 模块
    标准库 encoding/json 在使用时需要预先定义结构体,使用时显得不够灵活。这时候可以尝试使用 github.com/json-iterator/go 模块,其除了提供与标准库一样的接口之外,还提供了一系列更加灵活的操作方法。

    # 另辟蹊径提高性能的 easyjson 模块
    标准库 encoding/json 需要依赖反射来实现,因此性能上会比较差。 github.com/mailru/easyjson 则是利用 go generate 机制自动为结构体生成实现了 MarshalJSON 和 UnmarshalJSON 方法的代码,在序列化和解析时可以直接生成对应字段的 JSON 数据,而不需要运行时反射。据官方的介绍,其性能是标准库的 4~5 倍,是其他 json 模块的 2~3 倍。

    ## benchmark
    从以上结果可以看到, jsoniter 在序列化和解析时均有比较好的性能, easyjson 次之,标准库 json 则在解析时性能比较差。当然,这并不是一个比较严格的性能测试,比如没有考虑内存分配问题以及多种不同的 JSON 结构和数据长度的测试。但是,如果综合考虑性能和灵活性, jsoniter 可能是一个不错的选择。
    `

  2. 简化 Go 中对 JSON 的处理
    https://mp.weixin.qq.com/s/ND_W1g0k0tKZNpC2ohDR0w
    https://alanstorm.com/simplified-json-handling-in-go/
    `
    在强类型语言中,JSON 通常很难处理 —— JSON 类型有字符串、数字、字典和数组。如果你使用的语言是 javascript、python、ruby 或 PHP,那么 JSON 有一个很大的好处就是在解析和编码数据时你不需要考虑类型。

    在强类型语言中,你需要自己去定义怎么处理 JSON 对象的字符串、数字、字典和数组。在 Go 语言中,你使用内建的 API 时需要考虑如何更好地把一个 JSON 文件转换成 Go 的数据结构。

    解析/序列化为 map[string]interface
    解析/序列化为 struct

    SJSON 和 GJSON
    Go 内建的 JSON 处理并没有变化,但是已经出现了一些成熟的旨在用起来更简洁高效的处理 JSON 的包。

    SJSON(写 JSON)和 GJSON(读 JSON)是 Josh Baker 开发的两个包,你可以用来读写 JSON 字符串。
    `
    https://github.com/tidwall/sjson
    https://github.com/tidwall/gjson

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注