Golang高性能json包:easyjson

简介

easyjson是什么呢? 根据官网介绍,easyjson是提供高效快速且易用的结构体structs<-->json转换包。easyjson并没有使用反射方式实现,所以性能比其他的json包该4-5倍,比golang 自带的json包快2-3倍。 easyjson目标是维持生成去代码简单,以致于它可以轻松地进行优化或固定。

安装


go get -u github.com/mailru/easyjson/
go install  github.com/mailru/easyjson/easyjson
or
go build -o easyjson github.com/mailru/easyjson/easyjson

验证是否安装成功。

$ easyjson
Usage of D:\Code\go\bin\easyjson.exe:
  -all
        generate marshaler/unmarshalers for all structs in a file
  -build_tags string
        build tags to add to generated file
  -leave_temps
        do not delete temporary files
  -lower_camel_case
        use lowerCamelCase names instead of CamelCase by default
  -no_std_marshalers
        don't generate MarshalJSON/UnmarshalJSON funcs
  -noformat
        do not run 'gofmt -w' on output file
  -omit_empty
        omit empty fields by default

   string
        specify the filename of the output
  -pkg
        process the whole package instead of just the given file
  -snake_case
        use snake_case names instead of CamelCase by default
  -stubs
        only generate stubs for marshaler/unmarshaler funcs

其中有几个选项需要注意:

-lower_camel_case:将结构体字段field首字母改为小写。如Name=>name。  
-build_tags string:将指定的string生成到生成的go文件头部。  
-no_std_marshalers:不为结构体生成MarshalJSON/UnmarshalJSON函数。  
-omit_empty:没有赋值的field可以不生成到json,否则field为该字段类型的默认值。
-output_filename:定义生成的文件名称。
-pkg:对包内指定有`//easyjson:json`结构体生成对应的easyjson配置。
-snke_case:可以下划线的field如`Name_Student`改为`name_student`。

使用

记得在需要使用easyjson的结构体上加上//easyjson:json。 如下:

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

//easyjson:json
type Student struct {
    Id       int       `json:"id"`
    Name     string    `json:"s_name"`
    School   School    `json:"s_chool"`
    Birthday time.Time `json:"birthday"`
}

在结构体包下执行

easyjson  -all student.go

此时在该目录下出现一个新的文件。

// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.

package easyjson

import (
    json "encoding/json"
    easyjson "github.com/mailru/easyjson"
    jlexer "github.com/mailru/easyjson/jlexer"
    jwriter "github.com/mailru/easyjson/jwriter"
)

// suppress unused package warning
var (
    _ *json.RawMessage
    _ *jlexer.Lexer
    _ *jwriter.Writer
    _ easyjson.Marshaler
)

func easyjsonB83d7b77DecodeStudygoEasyjson(in *jlexer.Lexer, out *Student) {
    isTopLevel := in.IsStart()
    if in.IsNull() {
        if isTopLevel {
            in.Consumed()
        }
        in.Skip()
        return
    }
    in.Delim('{')
    for !in.IsDelim('}') {
        key := in.UnsafeString()
        in.WantColon()
        if in.IsNull() {
            in.Skip()
            in.WantComma()
            continue
        }
        switch key {
        case "id":
            out.Id = int(in.Int())
        case "s_name":
            out.Name = string(in.String())
        case "s_chool":
            easyjsonB83d7b77DecodeStudygoEasyjson1(in, &out.School)
        case "birthday":
            if data := in.Raw(); in.Ok() {
                in.AddError((out.Birthday).UnmarshalJSON(data))
            }
        default:
            in.SkipRecursive()
        }
        in.WantComma()
    }
    in.Delim('}')
    if isTopLevel {
        in.Consumed()
    }
}
func easyjsonB83d7b77EncodeStudygoEasyjson(out *jwriter.Writer, in Student) {
    out.RawByte('{')
    first := true
    _ = first
    if !first {
        out.RawByte(',')
    }
    first = false
    out.RawString("\"id\":")
    out.Int(int(in.Id))
    if !first {
        out.RawByte(',')
    }
    first = false
    out.RawString("\"s_name\":")
    out.String(string(in.Name))
    if !first {
        out.RawByte(',')
    }
    first = false
    out.RawString("\"s_chool\":")
    easyjsonB83d7b77EncodeStudygoEasyjson1(out, in.School)
    if !first {
        out.RawByte(',')
    }
    first = false
    out.RawString("\"birthday\":")
    out.Raw((in.Birthday).MarshalJSON())
    out.RawByte('}')
}

// MarshalJSON supports json.Marshaler interface
func (v Student) MarshalJSON() ([]byte, error) {
    w := jwriter.Writer{}
    easyjsonB83d7b77EncodeStudygoEasyjson(&w, v)
    return w.Buffer.BuildBytes(), w.Error
}

// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Student) MarshalEasyJSON(w *jwriter.Writer) {
    easyjsonB83d7b77EncodeStudygoEasyjson(w, v)
}

// UnmarshalJSON supports json.Unmarshaler interface
func (v *Student) UnmarshalJSON(data []byte) error {
    r := jlexer.Lexer{Data: data}
    easyjsonB83d7b77DecodeStudygoEasyjson(&r, v)
    return r.Error()
}

// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Student) UnmarshalEasyJSON(l *jlexer.Lexer) {
    easyjsonB83d7b77DecodeStudygoEasyjson(l, v)
}
func easyjsonB83d7b77DecodeStudygoEasyjson1(in *jlexer.Lexer, out *School) {
    isTopLevel := in.IsStart()
    if in.IsNull() {
        if isTopLevel {
            in.Consumed()
        }
        in.Skip()
        return
    }
    in.Delim('{')
    for !in.IsDelim('}') {
        key := in.UnsafeString()
        in.WantColon()
        if in.IsNull() {
            in.Skip()
            in.WantComma()
            continue
        }
        switch key {
        case "name":
            out.Name = string(in.String())
        case "addr":
            out.Addr = string(in.String())
        default:
            in.SkipRecursive()
        }
        in.WantComma()
    }
    in.Delim('}')
    if isTopLevel {
        in.Consumed()
    }
}
func easyjsonB83d7b77EncodeStudygoEasyjson1(out *jwriter.Writer, in School) {
    out.RawByte('{')
    first := true
    _ = first
    if !first {
        out.RawByte(',')
    }
    first = false
    out.RawString("\"name\":")
    out.String(string(in.Name))
    if !first {
        out.RawByte(',')
    }
    first = false
    out.RawString("\"addr\":")
    out.String(string(in.Addr))
    out.RawByte('}')
}

现在可以写一个测试类啦。


package main

import (
    "studygo/easyjson"
    "time"
    "fmt"
)

func main(){
    s:=easyjson.Student{
        Id: 11,
        Name:"qq",
        School:easyjson.School{
            Name:"CUMT",
            Addr:"xz",
        },
        Birthday:time.Now(),
    }
    bt,err:=s.MarshalJSON()
    fmt.Println(string(bt),err)
    json:=`{"id":11,"s_name":"qq","s_chool":{"name":"CUMT","addr":"xz"},"birthday":"2017-08-04T20:58:07.9894603+08:00"}`
    ss:=easyjson.Student{}
    ss.UnmarshalJSON([]byte(json))
    fmt.Println(ss)
}

运行结果:

{"id":11,"s_name":"qq","s_chool":{"name":"CUMT","addr":"xz"},"birthday":"2017-08-04T20:58:07.9894603+08:00"} <nil>
{121  {CwwwwwwwUMT xzwwwww} 2017-08-04 20:52:03.4066002 +0800 CST}

7 个评论

跟最近不错的json-iterator比起来怎么样呀
没有使用过json-iterato,不敢乱加评论
生成的代码里
```
func easyjsonB83d7b77EncodeStudygoEasyjson(out *jwriter.Writer, in Student) {
out.RawByte('{')
first := true
_ = first
if !first {
out.RawByte(',')
}

...
```

` _ = first` 这行是什么用意?
ns/op allocation bytes allocation times
std decode 35510 ns/op 1960 B/op 99 allocs/op
easyjson decode 8499 ns/op 160 B/op 4 allocs/op
jsoniter decode 5623 ns/op 160 B/op 3 allocs/op
std encode 2213 ns/op 712 B/op 5 allocs/op
easyjson encode 883 ns/op 576 B/op 3 allocs/op
jsoniter encode 837 ns/op 384 B/op 4 allocs/op

易用性上不如 jsoniter,那我选 jsoniter
--------------------
滴滴招聘 golang 攻城师,和 jsoniter 的作者大牛一起工作的好机会~
简历发至 caochunhui@didichuxing.com
有时间我试试看
go 开发组以后会不会怒改 json包
估计不会了。

感觉 go team 的心态就是“我给一个能用的,还不错的就行了,其他你们自己努力”。情况就类型于 fasthttp、各种 http router 之类的。

要回复文章请先登录注册