头图

Go 语言解析 JSON

本文将说明如何利用 Go 语言将 JSON 解析为结构体和数组,如果解析 JSON 的嵌入对象,如何将 JSON 的自定义属性名称映射到结构体,如何解析非结构化的 JSON 字符串。

JSON 解析为结构体

JSON 的结构是 key-value,最直观的就是将 JSON 解析为结构体,如下 JSON :

{
  "name": yuzhou1u,
  "age": 18
}

Go 语言中,提供了一个专门的包 encoding/json ,所以我们在使用这个 JSON 包之前需要在头文件导入:

package main

import (
  "encoding/json"
  "fmt"
)

然后,我们需要定义一个 Go 语言的结构体以便我们能与 JSON 一一对应,比如在 JSON 中我们定义了姓名 name 和年龄 age ,所以需要定义一个结构体(命名可以随意,但最好通俗易懂)的字段与 JSON 字符串中的键相匹配:

type Person struct {
  Name string
  Age int
}

然后使用 json.Umarshal() 函数来解析 JSON 字符串,完整代码如下:

package main

import (
  "encoding/json"
  "fmt"
)

type Person struct {
  Name string
  Age  int
}

func main() {
  var p Person

  jsonString := `{"name": "yuzhou1su",
          "age" : 18}`

  err := json.Unmarshal([]byte(jsonString), &p)

  if err == nil {
    fmt.Println(p.Name)
    fmt.Println(p.Age)
  } else {
    fmt.Println(err)
  }

}

现在来解释一下上面 main 函数的代码:

  • 定义一个 Person 的 p 对象
  • 因为我们没有把文件系统使用上,所以是定义了一个 jsonString 的 JSON 数据
  • 使用 json.Unmarshal() 函数能够解析 JSON 格式的数据。但需要将 JSON 字符串转换为字节切片,并将结果存储到 p 对象中。 使用需要使用 & 地址运算符传入人员的地址。
  • 如果解析有效,则 json.Unmarshal() 函数返回 nil,您现在可以找到存储在 person 变量中的值。
  • 确保将 Person 结构中每个字段的第一个字符大写。 如果字段名称以小写字母开头,则不会导出到当前包之外,并且字段对 json.Unmarshal() 函数不可见。

运行上述代码,打印在控制台中结果为:

yuzhou1su
18

JSON 解析为数组

通常 JSON 数据会包括一系列的对象数组,就像这样一个班级的数据:

[
  {
    "id": 1,
    "name": "张三"
    "age": 20
  },
  {
    "id": 2,
    "name": "李翠花"
    "age": 18
  },
  {
    "id": 3,
    "name": "王老五"
    "age": 25
  }
]

我们只需要定义一个 students[] 的数组,代码如下:

package main

import (
  "encoding/json"
  "fmt"
)

type Student struct {
  Id   int
  Name string
  Age  int
}

func main() {

  var students []Student
  myClass :=
    `[
      {
        "id": 1,
        "name": "张三",
        "age": 20
      },
      {
        "id": 2,
        "name": "李翠花",
        "age": 18
      },
      {
        "id": 3,
        "name": "王老五",
        "age": 25
      }
  ]`

  err := json.Unmarshal([]byte(myClass), &students)

  if err == nil {
    for _, student := range students {
      fmt.Print("\t\n", student.Id)
      fmt.Print("\t", student.Name)
      fmt.Print("\t", student.Age)
    }
  } else {
    fmt.Println(err)
  }
}

使用 for...range 迭代数组,然后运行上述代码:

$ go run main.go

1  张三  20
2  李翠花  18
3  王老五  25

解析 JSON 嵌入对象

JSON 字符串有时包含嵌入对象,比如:

{
  "name": "yuzhou1su",
  "age": 18,
  "address": {
    "road": "renmin south road",
    "street": "123 street",
    "city": "cs",
    "province": "hn",
    "country": "cn"
  }
}

address 就是属于内嵌对象,我们同样需要创建另一个 Address 结构体:

package main

import (
  "encoding/json"
  "fmt"
)

type Person struct {
  Name    string
  Age     int
  Address struct {
    Road     string
    Street   string
    City     string
    Province string
    Country  string
  }
}

func main() {
  var p Person

  jsonString := `
        {
            "name": "yuzhou1su",
            "age": 18,
            "address": {
              "road": "renmin south road",
              "street": "123 street",
              "city": "cs",
               "province": "hn",
              "country": "cn"
            }
        }`

  err := json.Unmarshal([]byte(jsonString), &p)

  if err == nil {
    fmt.Println(p.Name)
    fmt.Println(p.Age)
    fmt.Println(p.Address.Road)
    fmt.Println(p.Address.Street)
    fmt.Println(p.Address.City)
    fmt.Println(p.Address.Province)
    fmt.Println(p.Address.Country)
  } else {
    fmt.Println(err)
  }

}

输出结果:

yuzhou1su
18
renmin south road
123 street
cs
hn
cn

自定义属性名称的映射

有时 JSON 字符串中的键不能直接映射到 Go 中结构的成员。 比如:

{
  "base currency": "USD",
  "destination currency": "CNY"
}

请注意,此 JSON 字符串中的键中有空格。 如果你尝试将它直接映射到一个结构,你会遇到问题,因为 Go 中的变量名不能有空格。 要解决此问题,您可以使用结构字段标记(在结构中的每个字段之后放置的字符串文字),如下所示:

type Rates stuct {
  Base string `json:"base currency"`
  Symbol string `json:"destination currency"`
}
  • JSON 的 base currency 映射到 Go 中的 Base 字段
  • JSON 的 destination currency 映射到 Go 中 Symbol

整合如下:

package main

import (
  "encoding/json"
  "fmt"
)

type Rates struct {
  Base   string `json:"base currency"`
  Symbol string `json:"destination currency"`
}

func main() {

  jsonString := `
        {
            "base currency": "USD",
            "destination currency": "CNY"
        }`

  var rates Rates
  err := json.Unmarshal([]byte(jsonString), &rates)

  if err == nil {
    fmt.Println(rates.Base)
    fmt.Println(rates.Symbol)
  } else {
    fmt.Println(err)
  }

}

运行如下代码:

$ go run main.go
USD
CNY

非结构化数据的映射

前面几节展示了相对简单的 JSON 字符串。 然而,在现实世界中,您要操作的 JSON 字符串通常很大且非结构化。 此外,您可能只需要从 JSON 字符串中检索特定值。

考虑以下 JSON 字符串:

{
    "success": true,
    "timestamp": 1588779306,
    "base": "USD",
    "date": "2022-01-15",
    "rates": {
        "BNB": 0.00225,
        "BTC": 0.000020,
        "EUR": 0.879,
        "GBP": 0.733,
        "CNY": 6.36
    } 
}

如果我们还想把美元解析为其他币种,不至于重新定义整个结构体,可以采取定义一个接口:

var result map[string] interface{}

上面的语句创建了一个 map 类型的变量 result,它的 key 是 string 类型,每个对应的 value 都是 interface{} 类型。 这个空接口表示该值可以是任何类型:

为了解析这个 JSON 字符串,我们应该使用 json.Unmarshal() 函数:

json.Unmarshal([]byte(jsonString), &result)

因为 result 的类型是接口,所有可以传入任何类型:

  • 当解析 success 键的话可以使用 result["sucess"],解析为布尔型。
  • 当解析 timestamp 时可以解析为数字类型
  • 解析 rates 使用传入 rates 即可, 即 rates := result["rates"],解析为 map 类型

整个代码如下:

package main

import (
  "encoding/json"
  "fmt"
)

type Rates struct {
  Base   string `json:"base currency"`
  Symbol string `json:"destination currency"`
}

func main() {

  jsonString := `
        {
            "success": true,
            "timestamp": 1588779306,
            "base": "USD",
            "date": "2022-01-15",
            "rates": {
                "BNB": 0.00225,
                "BTC": 0.000020,
                "EUR": 0.879,
                "GBP": 0.733,
                "CNY": 6.36
          } 
        }`

  var result map[string]interface{}
  err := json.Unmarshal([]byte(jsonString), &result)
  if err == nil {
    fmt.Println(result["success"])
    rates := result["rates"]
    fmt.Println(rates)
  } else {
    fmt.Println(err)
  }

}

运行代码如下:

$ go run main.go
true
map[BNB:0.00225 BTC:2e-05 CNY:6.36 EUR:0.879 GBP:0.733]

总结

JSON 数据作为常见的数据格式,有着非常多的使用场景。本篇文章介绍了如何利用 Go 语言来解析 JSON 数据,如解析为结构体、数组、嵌入对象,解析自定义字段和解析非结构化数据。下一篇文章将介绍一下如果将 Go 语言的数据编码为 JSON 数据,敬请期待!


一起开启技术漂泊之旅
专注于后端技术分享,Keep Coding, Keep Loving. 热爱文学和技术,用有趣的知识武装头脑,分享简单的快乐

混迹于江湖,江湖却没有我的影子

72 声望
7 粉丝
0 条评论
推荐阅读
在 Golang 中执行 Shell 命令
在本教程中,我们将学习如何在 Golang 中执行shell命令(如 ls、mkdir 或 grep )。我们还将学习如何通过 stdin 和 stdout 传递 I/O 到正在运行的命令,以及管理长时间运行的命令。

宇宙之一粟

又一款眼前一亮的Linux终端工具!
今天给大家介绍一款最近发现的功能十分强大,颜值非常高的一款终端工具。这个神器我是在其他公众号文章上看到的,但他们都没把它的强大之处介绍明白,所以我自己体验一波后,再向大家分享自己的体验。

良许6阅读 1.9k

「刷起来」Go必看的进阶面试题详解
逃逸分析是Go语言中的一项重要优化技术,可以帮助程序减少内存分配和垃圾回收的开销,从而提高程序的性能。下面是一道涉及逃逸分析的面试题及其详解。

王中阳Go4阅读 2k评论 1

封面图
初学后端,如何做好表结构设计?
这篇文章介绍了设计数据库表结构应该考虑的4个方面,还有优雅设计的6个原则,举了一个例子分享了我的设计思路,为了提高性能我们也要从多方面考虑缓存问题。

王中阳Go4阅读 1.8k评论 2

封面图
一分钟搞明白!快速掌握 Go WebAssembly
最近因为各种奇怪的原因,更多的接触到了 WebAssembly。虽然之前很多博客也翻过写过各种文章,但总感觉欠些味道。于是今天梳理了一版,和大家一起展开学习。

煎鱼4阅读 2.3k

go 协程操作map导致的数据竞争及解决方法
有个查询结果集的操作,无可避免的需要在循环获取数据,然后将结果集放到 map 中,这个操作在压测的时候,没出现问题,发布到生产环境之后,开始偶现 fatal error: concurrent map read and map write 错误,导致...

hxd_5阅读 865评论 4

Linux终端居然也可以做文件浏览器?
大家好,我是良许。在抖音上做直播已经整整 5 个月了,我很自豪我一路坚持到了现在【笑脸】最近我在做直播的时候,也开始学习鱼皮大佬,直播写代码。当然我不懂 Java 后端,因此就写写自己擅长的 Shell 脚本。但...

良许1阅读 2.1k

混迹于江湖,江湖却没有我的影子

72 声望
7 粉丝
宣传栏