json NewDecoder 和 json Unmarshal 的区别

49 min read

json.NewDecoder 和 json.Unmarshal 都是 Go 语言内部提供的用于处理 JSON 数据的工具包。两者的共同点是都可以将 JSON 字符串转换为 Go 语言中的结构体或者映射类型。

不同的是,json.Unmarshal 函数适用于将 JSON 字符串解析成已知结构的 Go 类型,而 json.NewDecoder 函数则适用于流式读取 JSON 数据或者处理大型 JSON 数据。

具体来说:

  • 使用 json.Unmarshal 函数是比较简单的,它只需要将 JSON 字符串和一个指向 Go 中对应类型的指针参数传入即可。示例代码如下:
type Person struct {
    Name string `json:"name"`
    Age int `json:"age"`
}

jsonStr := `{"name": "Alice", "age": 30}`
var person Person
if err := json.Unmarshal([]byte(jsonStr), &person); err != nil {
    log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)
  • 使用 json.NewDecoder 则需要与 io.Reader 类型一起工作,它可以帮助你在读取 JSON 数据时进行更多的控制。示例代码如下:
type Person struct {
    Name string `json:"name"`
    Age int `json:"age"`
}

jsonStr := `{"name": "Alice", "age": 30}`

reader := strings.NewReader(jsonStr)
decoder := json.NewDecoder(reader)

var person Person
if err := decoder.Decode(&person); err != nil {
    log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)

注意到这里我们使用了 strings.NewReader 函数将 JSON 字符串包装成了一个 io.Reader 对象,然后将其传递给了 json.NewDecoder 函数。后者会解析输入的 JSON 数据并将其存储在对应的 Go 结构体中。这个模式适用于需要从多个来源读取 JSON 数据的场景,例如从网络、磁盘或者标准输入读取。

json.Unmarshal 函数适用于简单的 JSON 解析场景,而 json.NewDecoder 更适合流式读取和解析 JSON 数据,包括处理大型的 JSON 数据。

什么是流式读取和解析 JSON 数据

json.NewDecoder 更适合流式读取是指它能够逐步地读取 JSON 数据,而不需要一次性加载整个 JSON 内容到内存中。这使得它在处理大量数据或需要持续从输入源读取数据时非常有用。此外,json.NewDecoder 还支持从 io.Reader、bufio.Reader、os.File 等多种输入源中读取数据,因此它也非常灵活。

strings.NewReader 函数

字符串的 Reader 是一个实现了 io.Reader 接口的对象,它可以读取一个字符串并返回一个流。当你需要使用某个需要实现 io.Reader 的函数来操作字符串时,可以使用 strings.NewReader 函数来将该字符串转换为一个 io.Reader 对象。

该函数的函数签名如下:

func NewReader(s string) *strings.Reader

该函数接受一个字符串作为参数,返回一个指向一个新的 strings.Reader 对象的指针。返回的 Reader 对象可以调用 Read 方法来从字符串中读取数据流。

示例代码:

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, world!"
    r := strings.NewReader(s)
    b := make([]byte, 5)
    _, err := r.Read(b)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(b))  // Output: Hello
}

在上面的代码中,我们首先定义了一个字符串 s,然后使用 strings.NewReader 函数将该字符串创建为一个 Reader 对象 r。

接下来,我们创建了一个切片 b 作为读取缓冲器,调用 r.Read 方法读取五个字节数据到 b 中,并将其转换为字符串输出。输出结果为:Hello。

需要注意的是,当 Reader 对象中的所有数据都已经被读取完毕时,再次调用 Read 方法将会返回一个 EOF 错误。因此,我们需要检查 Read 方法的返回值来确定是否已经读取完全部数据。

Read 方法的返回值来确定是否已经读取完全部数据

可以在每次读取时判断返回的错误是否为 EOF 错误。如果是,则表示已经读取完全部数据。代码示例:

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // 打开文件
    file, err := os.Open("test.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // 创建一个 bufio.Reader 对象
    reader := bufio.NewReader(file)

    // 循环读取数据
    for {
        // 读取一行数据
        line, err := reader.ReadString('\n')
        if err != nil {
            if err.Error() == "EOF" {
                // 已经读取完全部数据
                break
            } else {
                panic(err)
            }
        }
        // 输出读取的数据
        fmt.Print(line)
    }
}