背景
業務で構造体で定義したlogをJSON形式で標準出力にParseする実装をする場面があった。そのときに今回紹介するeasyjsonというGoのパッケージを使用したので、その使い方を備忘録としてこの記事に残しておく。
easyjsonとは
JSONとGo Structを相互に変換できる
easyjsonというパッケージを使えば、GoのstructとJSONをMarshal/Unmarshalするためのコードを自動生成することができる。
Marshal/Unmarshal とは
marshalを直訳すると「元帥」という意味になる。プログラミングにおける意味はこれではない。プログラミングにおける意味として、あるプログラムのデータ形式を他のプログラムで動作可能な状態に変換することを指す。恐らく今回はこの意味がもっとも近いと思われる。つまりGoのstructからJSONに変換することをMarshalと言っているっぽい。
easyjsonをダウンロード
次のコマンドを叩けばeasyjsonをダウンロードできる。
go get -u github.com/mailru/easyjson/...
easyjsonを使ってみる
次のstructを定義したファイルを用意する。
$ cat hoge.go
package hoge
type Hoge1 struct {
A string `json:"a"`
B *Hoge2
}
type Hoge2 struct {
C []int `json:"c"`
D map[string]int `json:"d"`
}
次のコマンドを実行するとGoのstructとJSONをMarshal/Unmarshalする為のファイルが自動生成される。
$ easyjson -all hoge.go
このコマンドを叩くとhoge_easyjsonというファイルが生成される。
$ cat hoge_easyjson
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package hoge
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 easyjson154013cbDecodeTestProjectHoge(in *jlexer.Lexer, out *Hoge2) {
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 "c":
if in.IsNull() {
in.Skip()
out.C = nil
} else {
in.Delim('[')
if out.C == nil {
if !in.IsDelim(']') {
out.C = make([]int, 0, 8)
} else {
out.C = []int{}
}
} else {
out.C = (out.C)[:0]
}
for !in.IsDelim(']') {
var v1 int
v1 = int(in.Int())
out.C = append(out.C, v1)
in.WantComma()
}
in.Delim(']')
}
case "d":
if in.IsNull() {
in.Skip()
} else {
in.Delim('{')
out.D = make(map[string]int)
for !in.IsDelim('}') {
key := string(in.String())
in.WantColon()
var v2 int
v2 = int(in.Int())
(out.D)[key] = v2
in.WantComma()
}
in.Delim('}')
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson154013cbEncodeTestProjectHoge(out *jwriter.Writer, in Hoge2) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"c\":"
out.RawString(prefix[1:])
if in.C == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v3, v4 := range in.C {
if v3 > 0 {
out.RawByte(',')
}
out.Int(int(v4))
}
out.RawByte(']')
}
}
{
const prefix string = ",\"d\":"
out.RawString(prefix)
if in.D == nil && (out.Flags&jwriter.NilMapAsEmpty) == 0 {
out.RawString(`null`)
} else {
out.RawByte('{')
v5First := true
for v5Name, v5Value := range in.D {
if v5First {
v5First = false
} else {
out.RawByte(',')
}
out.String(string(v5Name))
out.RawByte(':')
out.Int(int(v5Value))
}
out.RawByte('}')
}
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v Hoge2) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson154013cbEncodeTestProjectHoge(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Hoge2) MarshalEasyJSON(w *jwriter.Writer) {
easyjson154013cbEncodeTestProjectHoge(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Hoge2) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson154013cbDecodeTestProjectHoge(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Hoge2) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson154013cbDecodeTestProjectHoge(l, v)
}
func easyjson154013cbDecodeTestProjectHoge1(in *jlexer.Lexer, out *Hoge1) {
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 "a":
out.A = string(in.String())
case "B":
if in.IsNull() {
in.Skip()
out.B = nil
} else {
if out.B == nil {
out.B = new(Hoge2)
}
(*out.B).UnmarshalEasyJSON(in)
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson154013cbEncodeTestProjectHoge1(out *jwriter.Writer, in Hoge1) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"a\":"
out.RawString(prefix[1:])
out.String(string(in.A))
}
{
const prefix string = ",\"B\":"
out.RawString(prefix)
if in.B == nil {
out.RawString("null")
} else {
(*in.B).MarshalEasyJSON(out)
}
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v Hoge1) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson154013cbEncodeTestProjectHoge1(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Hoge1) MarshalEasyJSON(w *jwriter.Writer) {
easyjson154013cbEncodeTestProjectHoge1(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Hoge1) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson154013cbDecodeTestProjectHoge1(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Hoge1) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson154013cbDecodeTestProjectHoge1(l, v)
}
これでGoのstructをJSONに変換するプログラムが準備できた。では実際に変換してみる。やり方は簡単でJSONに変換したいstructをMarshal
というメソッドの引数に指定すれば良い。
$ cat main.go
package main
import (
"fmt"
"test-project/hoge"
easyjson "github.com/mailru/easyjson"
)
func main() {
a := hoge.Hoge1{A: "hogeA", B: &hoge.Hoge2{
C: []int{1, 2, 3},
}}
b, err := easyjson.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(a) // {hogeA 0xc0001201e0}
fmt.Println(string(b)) // {"a":"hoge string","B":{"c":[1,2,3],"d":null}}
}
Marshal
にGoの構造体を渡すだけで、JSONに変換できていることがわかる。
細かいオプションの設定はREADMEを見れば良い。