mailru/easyjson
GitHub: mailru/easyjson
easyjson 是一个基于代码生成的 Go 语言高性能 JSON 序列化/反序列化库,通过避免运行时反射实现比标准库快数倍的编解码速度。
Stars: 4906 | Forks: 465
# easyjson [](https://github.com/mailru/easyjson/actions/workflows/easyjson.yml) [](https://goreportcard.com/report/github.com/mailru/easyjson)
easyjson 包提供了一种快速简便的方法,可以在不使用反射的情况下,对 Go 结构体进行 JSON 的 marshal/unmarshal。在性能测试中,easyjson 的速度是标准 `encoding/json` 包的 4-5 倍,是其他 JSON 编码包的 2-3 倍。
easyjson 旨在使生成的 Go 代码足够简单,以便于轻松优化或修复。另一个目标是提供标准 `encoding/json` 包中不可用的选项来定制生成的代码,例如生成 "snake_case" 名称或默认启用 `omitempty` 行为。
## 用法
### 安装:
```
# 适用于 Go < 1.17
go get -u github.com/mailru/easyjson/...
```
#### 或者
```
# 适用于 Go >= 1.17
go get github.com/mailru/easyjson && go install github.com/mailru/easyjson/...@latest
```
### 运行:
```
easyjson -all .go
```
以上命令将生成 `_easyjson.go` 文件,其中包含 `.go` 中所有结构体对应的 marshaler 和 unmarshaler 函数。
请注意,easyjson 需要完整的 Go 构建环境,并设置 `GOPATH` 环境变量。这是因为 easyjson 的代码生成会在一个临时文件上调用 `go run`(这是一种借鉴自 [ffjson](https://github.com/pquerna/ffjson) 的代码生成方法)。
### 序列化
```
someStruct := &SomeStruct{Field1: "val1", Field2: "val2"}
rawBytes, err := easyjson.Marshal(someStruct)
```
### 反序列化
```
someStruct := &SomeStruct{}
err := easyjson.Unmarshal(rawBytes, someStruct)
```
请参阅 [GoDoc](https://godoc.org/github.com/mailru/easyjson) 获取更多信息和功能。
## 选项
```
Usage of easyjson:
-all
generate marshaler/unmarshalers for all structs in a file
-build_tags string
build tags to add to generated file
-gen_build_flags string
build flags when running the generator while bootstrapping
-byte
use simple bytes instead of Base64Bytes for slice of bytes
-leave_temps
do not delete temporary files
-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
-output_filename 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
-lower_camel_case
use lowerCamelCase instead of CamelCase by default
-stubs
only generate stubs for marshaler/unmarshaler funcs
-disallow_unknown_fields
return error if some unknown field in json appeared
-disable_members_unescape
disable unescaping of \uXXXX string sequences in member names
```
使用 `-all` 将为文件中的所有 Go 结构体生成 marshaler/unmarshaler,但不包括前面注释以 `easyjson:skip` 开头的结构体。
例如:
```
//easyjson:skip
type A struct {}
```
如果未提供 `-all`,则只会为前面注释以 `easyjson:json` 开头的结构体生成 marshaler/unmarshaler。例如:
```
//easyjson:json
type A struct {}
```
附加选项说明:
* `-snake_case` 告诉 easyjson 默认生成 snake\_case 字段名(除非被字段标签覆盖)。CamelCase 到 snake\_case 的转换算法在大多数情况下都有效(例如,HTTPVersion 将被转换为 "http_version")。
* `-build_tags` 会将指定的构建标签添加到生成的 Go 源码中。
* `-gen_build_flags` 将使用提供的标志执行 easyjson 引导代码以启动实际的生成器命令。多个参数应由空格分隔,例如 `-gen_build_flags="-mod=mod -x"`。
## 结构体 json 标签选项
除了像 'omitempty' 这样的标准 json 标签选项外,还支持以下选项:
* 'nocopy' - 禁用 string 值的分配和复制,使它们引用原始 json 缓冲区内存。这对于解码后立即使用、不会长期保存在内存中的短生命周期对象非常有效。请注意,如果 string 需要反转义,它将按常规方式处理。
* 'intern' - string "interning"(去重),当整个结构中经常遇到相同的字符串字典值时,可以节省内存。
有关更多详细信息,请参见下文。
## 生成的 Marshaler/Unmarshaler 函数
对于 Go 结构体类型,easyjson 会生成 `MarshalEasyJSON` / `UnmarshalEasyJSON` 函数用于 JSON 的 marshal/unmarshal。反过来,这些函数实现了 `easyjson.Marshaler` 和 `easyjson.Unmarshaler` 接口,当与 `easyjson.Marshal` / `easyjson.Unmarshal` 结合使用时,可以避免在 Go 结构体进行 JSON marshal/unmarshal 时进行不必要的反射 / 类型断言。
easyjson 还会为 Go 结构体类型生成 `MarshalJSON` 和 `UnmarshalJSON` 函数,它们与标准的 `json.Marshaler` 和 `json.Unmarshaler` 接口兼容。请注意,与使用 `easyjson.Marshal` / `easyjson.Unmarshal` 相比,使用标准的 `json.Marshal` / `json.Unmarshal` 进行 marshal/unmarshal 会导致显著的性能下降。
此外,easyjson 公开了实用工具函数,它们使用 `MarshalEasyJSON` 和 `UnmarshalEasyJSON` 对标准的 reader 和 writer 进行 marshal/unmarshal。例如,easyjson 提供了 `easyjson.MarshalToHTTPResponseWriter`,它可以将数据序列化到标准的 `http.ResponseWriter`。请参阅 [GoDoc 列表](https://godoc.org/github.com/mailru/easyjson) 获取可用实用工具函数的完整列表。
## 控制 easyjson 的序列化和反序列化行为
Go 类型可以提供自己的 `MarshalEasyJSON` 和 `UnmarshalEasyJSON` 函数,以满足 `easyjson.Marshaler` / `easyjson.Unmarshaler` 接口。当为 Go 类型定义了这些函数时,`easyjson.Marshal` 和 `easyjson.Unmarshal` 将使用它们。
Go 类型还可以实现 `easyjson.Optional` 接口,这允许该类型定义自己的 `omitempty` 逻辑。
## 类型包装器
easyjson 提供了定义在 `easyjson/opt` 包中的额外类型包装器。它们包装了标准的 Go 基础类型,并实现了 easyjson 接口。
当需要区分缺失值和/或需要指定默认值时,`easyjson/opt` 类型包装器非常有用。类型包装器允许 easyjson 避免额外的指针和堆分配,如果使用得当,可以显著提高性能。
## 内存池
easyjson 使用了一个缓冲池,它以从 128 到 32768 字节递增的块来分配数据。512 字节及以上的块将借助 `sync.Pool` 被重复使用。块的最大大小是有界限的,以减少冗余的内存分配并允许使用更大的可重用缓冲区。
easyjson 的自定义分配缓冲池定义在 `easyjson/buffer` 包中,并且(如有必要)可以在任何 marshal 或 unmarshal 之前通过调用 `buffer.Init()` 来修改默认的缓冲池行为。
请参阅 [GoDoc 列表](https://godoc.org/github.com/mailru/easyjson/buffer) 获取更多信息。
## 字符串驻留(Interning)
在 unmarshal 期间,可以选择对 `string` 字段值进行 [interned](https://en.wikipedia.org/wiki/String_interning) 处理,通过在内存中去重字符串来减少内存分配和使用,代价是 CPU 使用率会略微增加。
这仅对频繁具有相同值的正在解码的 `string` 字段有效(例如,如果您有一个只能取少量可能值的 string 字段)。
要启用 string interning,请在 `string` 字段上的 `json` 标签中添加 `intern` 关键字标签,例如:
```
type Foo struct {
UUID string `json:"uuid"` // will not be interned during unmarshaling
State string `json:"state,intern"` // will be interned during unmarshaling
}
```
## 问题、说明和限制
* easyjson 仍处于早期开发阶段。因此,与 `encoding/json` 相比,可能存在 bug 或缺少功能。如果遇到缺少功能或 bug,请创建 GitHub issue。欢迎提交 Pull request!
* 与 `encoding/json` 不同,对象键是区分大小写的。由于在进行不区分大小写的键匹配时会严重影响性能,因此目前未提供不区分大小写的匹配。将来可能会通过生成器的选项提供不区分大小写的对象键匹配。
* easyjson 使用了 `unsafe`,这简化了代码,并通过允许从 `[]byte` 到 `string` 的无拷贝转换提供了显著的性能优势。也就是说,`unsafe` 仅在 unmarshal 和解析 JSON 时使用,并且任何 `unsafe` 操作 / 内存分配都会被 easyjson 安全地释放。设置构建标签 `easyjson_nounsafe` 可以在编译时不使用 `unsafe`。
* easyjson 兼容 Google App Engine。`appengine` 构建标签(由 App Engine 的环境设置)将自动禁用 `unsafe` 的使用,因为 App Engine 的标准环境不允许使用 `unsafe`。请注意,在 App Engine 中的使用仍然是实验性的。
* 浮点数使用 Go 的 `strconv` 包的默认精度进行格式化。因此,easyjson 在对 JSON 进行 marshal/unmarshal 时将无法正确处理高精度浮点数。但请注意,在极少/有限的情况下,此行为对于一般使用而言是不够的。也就是说,如果需要精确地对高精度浮点数进行 JSON 的 marshal/unmarshal,可能需要使用其他包。
* 在 unmarshal 时,JSON 解析器只做跳过不匹配的括号所需的最少工作,因此不会对正在 unmarshal/解析的整个 JSON 值进行完整验证。
* 目前不支持真正的流式编码/解码,因为通常对于许多使用场景/协议来说,在发送数据之前需要知道最终序列化后的 JSON 长度。目前 easyjson 的架构无法实现这一点。
* easyjson 解析器和代码生成基于反射,因此它无法在 `package main` 文件上运行,因为解析器无法导入它们。
## 基准测试
大多数基准测试是使用这个 [13kB JSON 示例](https://dev.twitter.com/rest/reference/get/search/tweets)(消除空格后为 9k)进行的。此示例类似于真实世界的数据,结构良好,并包含各种不同的类型,使其非常适合 JSON 序列化基准测试。
注意:
* 对于小请求基准测试,使用了上述示例中 80 字节的部分。
* 对于大请求 marshal 基准测试,使用了包含 50 个常规样本的结构体,生成了约 500kB 的输出 JSON。
* 基准测试展示了 easyjson 默认行为的结果,该行为使用了 `unsafe`。
基准测试可在代码仓库中找到,可以通过调用 `make` 来运行。
### easyjson 对比 encoding/json
easyjson 在 unmarshal 方面比标准的 `encoding/json` 快大约 5-6 倍,在非并发 marshal 方面快 3-4 倍。如果序列化到 writer,并发 marshal 快 6-7 倍。
### easyjson 对比 ffjson
easyjson 使用了与 [ffjson](https://github.com/pquerna/ffjson) 相同的 JSON marshal 方法,但在 unmarshal 期间对 JSON 的词法分析和解析采用了截然不同的方法。这意味着 easyjson 在 unmarshal 方面快了大约 2-3 倍,在非并发 unmarshal 方面快了 1.5-2 倍。
截至撰写本文时,`ffjson` 在并发使用时似乎存在问题:具体而言,大请求池化损害了 `ffjson` 的性能并导致了可扩展性问题。`ffjson` 的这些问题很可能可以修复,但在撰写本文时,它们仍是 `ffjson` 未解决/已知的问题。
对于小请求,easyjson 和 `ffjson` 具有相似的性能,但是当与 writer 一起用于大请求时,easyjson 的速度大约比 `ffjson` 快 2-5 倍。
### easyjson 对比 go/codec
[go/codec](https://github.com/ugorji/go) 为 JSON 生成提供了编译时辅助工具。在这种情况下,辅助工具不像 marshaler 那样工作,因为它们是独立于编码的。
在非并发基准测试中,easyjson 通常比 `go/codec` 快 2 倍,在并发编码(不 marshal 到 writer)方面大约快 3 倍。
为了尝试测量 `go/codec` 的 marshal 性能(而不是分配/memcpy/writer 接口调用),进行了一项基准测试,即重置 byte slice 的长度而不是将整个 slice 重置为 nil。然而,这种确切形式的优化在实践中可能并不适用,因为在 marshal 操作之间内存没有被释放。
### easyjson vs 'ujson' python 模块
[ujson](https://github.com/esnme/ultrajson) 使用 C 代码进行解析,因此看看纯 golang 与之相比如何是很有趣的。需要注意的是,由于该库将 JSON 对象解析为字典,因此访问 python 生成的对象速度较慢。
easyjson 在 unmarshal 方面略快,而在 marshal 方面比 `ujson` 快 2-3 倍。
### 基准测试结果
`ffjson` 的结果截至 2016 年 2 月 4 日,使用最新的 `ffjson` 和 go1.6。
`go/codec` 的结果截至 2016 年 3 月 4 日,使用最新的 `go/codec` 和 go1.6。
#### Unmarshal (反序列化)
| lib | json 大小 | MB/s | allocs/op | B/op |
|:---------|:----------|-----:|----------:|------:|
| standard | regular | 22 | 218 | 10229 |
| standard | small | 9.7 | 14 | 720 |
| | | | | |
| easyjson | regular | 125 | 128 | 9794 |
| easyjson | small | 67 | 3 | 128 |
| | | | | |
| ffjson | regular | 66 | 141 | 9985 |
| ffjson | small | 17.6 | 10 | 488 |
| | | | | |
| codec | regular | 55 | 434 | 19299 |
| codec | small | 29 | 7 | 336 |
| | | | | |
| ujson | regular | 103 | N/A | N/A |
#### Marshal (序列化),单个 goroutine。
| lib | json 大小 | MB/s | allocs/op | B/op |
|:----------:----------|-----:|----------:|------:|
| standard | regular | 75 | 9 | 23256 |
| standard | small | 32 | 3 | 328 |
| standard | large | 80 | 17 | 1.2M |
| | | | | |
| easyjson | regular | 213 | 9 | 10260 |
| easyjson* | regular | 263 | 8 | 742 |
| easyjson | small | 125 | 1 | 128 |
| easyjson | large | 212 | 33 | 490k |
| easyjson* | large | 262 | 25 | 2879 |
| | | | | |
| ffjson | regular | 122 | 153 | 21340 |
| ffjson** | regular | 146 | 152 | 4897 |
| ffjson | small | 36 | 5 | 384 |
| ffjson** | small | 64 | 4 | 128 |
| ffjson | large | 134 | 7317 | 818k |
| ffjson** | large | 125 | 7320 | 827k |
| | | | | |
| codec | regular | 80 | 17 | 33601 |
| codec*** | regular | 108 | 9 | 1153 |
| codec | small | 42 | 3 | 304 |
| codec*** | small | 56 | 1 | 48 |
| codec | large | 73 | 483 | 2.5M |
| codec*** | large | 103 | 451 | 66007 |
| | | | | |
| ujson | regular | 92 | N/A | N/A |
\* 序列化到 writer,
\*\* 使用 `ffjson.Pool()`,
\*\*\* 重用输出 slice 而不是将其重置为 nil
#### Marshal (序列化),并发。
| lib | json 大小 | MB/s | allocs/op | B/op |
|:----------|:----------|-----:|----------:|------:|
| standard | regular | 252 | 9 | 23257 |
| standard | small | 124 | 3 | 328 |
| standard | large | 289 | 17 | 1.2M |
| | | | | |
| easyjson | regular | 792 | 9 | 10597 |
| easyjson* | regular | 1748 | 8 | 779 |
| easyjson | small | 333 | 1 | 128 |
| easyjson | large | 718 | 36 | 548k |
| easyjson* | large | 2134 | 25 | 4957 |
| | | | | |
| ffjson | regular | 301 | 153 | 21629 |
| ffjson** | regular | 707 | 152 | 5148 |
| ffjson | small | 62 | 5 | 384 |
| ffjson** | small | 282 | 4 | 128 |
| ffjson | large | 438 | 7330 | 1.0M |
| ffjson** | large | 131 | 7319 | 820k |
| | | | | |
| codec | regular | 183 | 17 | 33603 |
| codec*** | regular | 671 | 9 | 1157 |
| codec | small | 147 | 3 | 304 |
| codec*** | small | 299 | 1 | 48 |
| codec | large | 190 | 483 | 2.5M |
| codec*** | large | 752 | 451 | 77574 |
\* 序列化到 writer,
\*\* 使用 `ffjson.Pool()`,
\*\*\* 重用输出 slice 而不是将其重置为 nil
标签:EVTX分析, Golang, Homebrew安装, JSON, SOC Prime, 代码生成, 安全编程, 序列化, 开发工具, 性能优化, 日志审计, 检测绕过, 渗透测试工具