44

Go 语言操作 JSON 的几种方法 - 早起搬砖 morning.work

 4 years ago
source link: https://morning.work/page/go/json.html?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

标准库的 json 模块

Go 语言标准库 encoding/json 提供了操作 JSON 的方法,一般可以使用 json.Marshaljson.Unmarshal 来序列化和解析 JSON 字符串:

// 定义结构体
type User struct {
    Email    string `json:"email"`
    Password string `json:"password"`
}

// 序列化
buf, err := json.Marshal(User{
    Email:    "[email protected]",
    Password: "123456",
})

// 解析
user := User {}
err := json.Unmarshal([]byte(`{
  "email": "[email protected]",
  "password": "123456"
}`), &user)

更加灵活和更好性能的 jsoniter 模块

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

val := []byte(`{"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}`)

// 仅解析 Colors 字段,并直接得到 string 类型
str := jsoniter.Get(val, "Colors", 0).ToString()

另辟蹊径提高性能的 easyjson 模块

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

要使用 easyjson 模块,首先执行以下命令安装 easyjson 命令:

go get -u github.com/mailru/easyjson/...

然后新建文件 school.go ,并定义结构体 School

//easyjson:json
type School struct {
    Name string     `json:"name"`
    Addr string     `json:"addr"`
}

接着执行 easyjson -all school.go ,此时目录下会生成一个新的文件 school_easyjson.go ,为 School 结构体实现了 MarshalJSONUnmarshalerJSON 方法,接着使用 easyjson 对应的方法去对这个结构体进行解析即可。

简单性能测试结果

对于以上介绍的三个模块,我测试了对于以下 JSON 字符串其序列化和解析性能的测试结果。

测试程序:

package awesomeProject

import (
    "encoding/json"
    "testing"

    jsoniter "github.com/json-iterator/go"
    "github.com/mailru/easyjson"
)

type T1 struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Intro string `json:"intro"`
    Valid bool   `json:"valid"`
}

// easyjson:json
type T2 struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Intro string `json:"intro"`
    Valid bool   `json:"valid"`
}

var d1 = T1{Name: "如何快速提升", Age: 12320, Intro: "如何快速提升 Go 程序性能?如何快速提升 Go 程序性能?如何快速提升 Go 程序性能?Well, easyJson is 4 times faster than normal json(as per its documets) ,in our organization we have used it extensively and yes its faster. Here is a small example to get started. my current directory name is easyJson", Valid: true}
var d2 = T2{Name: "如何快速提升", Age: 12320, Intro: "如何快速提升 Go 程序性能?如何快速提升 Go 程序性能?如何快速提升 Go 程序性能?Well, easyJson is 4 times faster than normal json(as per its documets) ,in our organization we have used it extensively and yes its faster. Here is a small example to get started. my current directory name is easyJson", Valid: true}
var s1 []byte

func init() {
    buf, err := json.Marshal(&d1)
    if err != nil {
        panic(err)
    }
    s1 = buf
    println(string(s1))
}

func BenchmarkStdJsonMarshal(b *testing.B) {
    i := 0
    for i < b.N {
        i++
        _, err := json.Marshal(&d1)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkStdJsonUnmarshal(b *testing.B) {
    i := 0
    var d T1
    for i < b.N {
        i++
        err := json.Unmarshal(s1, &d)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkJsoniterMarshal(b *testing.B) {
    i := 0
    for i < b.N {
        i++
        _, err := jsoniter.Marshal(&d1)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkJsoniterUnmarshal(b *testing.B) {
    i := 0
    var d T1
    for i < b.N {
        i++
        err := jsoniter.Unmarshal(s1, &d)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkJsoniterAny(b *testing.B) {
    i := 0
    var d T1
    for i < b.N {
        i++
        a := jsoniter.Get(s1)
        d.Name = a.Get("name").ToString()
        d.Age = a.Get("age").ToInt()
        d.Intro = a.Get("intro").ToString()
        d.Valid = a.Get("valid").ToBool()
    }
}

func BenchmarkJsoniterAnyLight(b *testing.B) {
    i := 0
    var d T1
    for i < b.N {
        i++
        a := jsoniter.Get(s1)
        d.Name = a.Get("name").ToString()
    }
}

func BenchmarkEasyJsonMarshal(b *testing.B) {
    i := 0
    for i < b.N {
        i++
        _, err := easyjson.Marshal(d2)
        if err != nil {
            panic(err)
        }
    }
}

func BenchmarkEasyJsonUnmarshal(b *testing.B) {
    i := 0
    var d T2
    for i < b.N {
        i++
        err := easyjson.Unmarshal(s1, &d)
        if err != nil {
            panic(err)
        }
    }
}

JSON 字符串:

{
  "name":"如何快速提升",
  "age":12320,
  "intro":"如何快速提升 Go 程序性能?如何快速提升 Go 程序性能?如何快速提升 Go 程序性能?Well, easyJson is 4 times faster than normal json(as per its documets) ,in our organization we have used it extensively and yes its faster. Here is a small example to get started. my current directory name is easyJson",
  "valid":true
}

测试结果:

  • BenchmarkStdJsonMarshal-4,使用标准库序列化,1098 ns/op
  • BenchmarkStdJsonUnmarshal-4,使用标准库解析,5006 ns/op
  • BenchmarkJsoniterMarshal-4,使用 jsoniter 序列化,1106 ns/op
  • BenchmarkJsoniterUnmarshal-4,使用 jsoniter 解析,816 ns/op
  • BenchmarkJsoniterAny-4,使用 jsoniter 的 Any 解析,4092 ns/op
  • BenchmarkJsoniterAnyLight-4,使用 jsoniter 的 Any 解析一个字段,1263 ns/op
  • BenchmarkEasyJsonMarshal-4,使用 easyjson 序列化,1658 ns/op
  • BenchmarkEasyJsonUnmarshal-4,使用 easyjson 解析,952 ns/op

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK