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

本文最后更新于2018年10月31日,已超过 1 年没有更新,如果文章内容失效,还请反馈给我,谢谢!

=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=

声明: 除非注明,ixyzero.com文章均为原创,转载请以链接形式标明本文地址,谢谢!
https://ixyzero.com/blog/archives/4151.html

4 thoughts on “Go语言学习#11-如何解析json字符串”

  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 可能是一个不错的选择。
    `

发表评论

电子邮件地址不会被公开。 必填项已用*标注