easyjsonを使ってみた
2020-10-21
こんにちは
どうも、[僕](https://www.takurinton.com)です。
この記事は随分前のインターン期間中に自分のために書いた記事を転載してます。
GoでJSON使う時ってだいぶめんどくさいんですよね。まあ型による安心感がバケモンなのでやった方がいいんですけど。
GoでJSONを捌く時はstructを使用します。
クラスとかはないのでこれでいきます。
Unmarshal
早速やってみます。
例えばこんな感じのjsonがくるとします。
var req string
req = `[
{"name": "takurinton", "age": 20, "favorite": ["runnning", "baseball"]},
{"name": "ryota", "age": 16, "favorite": ["fishing", "baseball"]},
{"name": "hoge", "age": 26, "favorite": ["programming"]},
{"name": "fuga", "age": 10, "favorite": ["study", "programming"]}
]`
それをGoで扱える形に変換したい時はこんな感じで書いてあげます。
package main
type Human struct {
Name string
Age int
Favorite []string
}
func main() {
var req string
req = `[
{"name": "takurinton", "age": 20, "favorite": ["runnning", "baseball"]},
{"name": "ryota", "age": 16, "favorite": ["fishing", "baseball"]},
{"name": "hoge", "age": 26, "favorite": ["programming"]},
{"name": "fuga", "age": 10, "favorite": ["study", "programming"]}
]`
bytes := []byte(req) // byte型に変換
var human []Human
if err := json.Unmarshal(bytes, &human); err != nil {
log.Fatal(err)
}
for _, h := range human {
fmt.Printf("name: %s, age: %d, favorite: %v\n ", h.Name, h.Age, h.Favorite)
}
}
name: takurinton, age: 20, favorite: [runnning baseball]
name: ryota, age: 16, favorite: [fishing baseball]
name: hoge, age: 26, favorite: [programming]
name: fuga, age: 10, favorite: [study programming]
Goには[Unmarshal](https://golang.org/pkg/encoding/json/#Unmarshal)というjsonをサポートしてくれる関数が準備されていて、これを利用することでよしなに変換してくれるわけです。
こやつはこんな感じの構造をしてます。第一引数はbyteのスライス、第二引数はインターフェイスを渡してあげます。上のプログラムでもしっかり値を渡すことができています。
func Unmarshal(data []byte, v interface{}) error
Marshal
逆もできます。
func main() {
var req string
req = `[
{"name": "takurinton", "age": 20, "favorite": ["runnning", "baseball"]},
{"name": "ryota", "age": 16, "favorite": ["fishing", "baseball"]},
{"name": "hoge", "age": 26, "favorite": ["programming"]},
{"name": "fuga", "age": 10, "favorite": ["study", "programming"]}
]`
bytes := []byte(req) // byte型に変換
var human []Human
if err := json.Unmarshal(bytes, &human); err != nil {
log.Fatal(err)
}
h, err := json.Marshal(human)
if err != nil {
log.Fatal(err)
}
res := string(h) // stringに変換
fmt.Println(res)
}
[{"Name":"takurinton","Age":20,"Favorite":["runnning","baseball"]},{"Name":"ryota","Age":16,"Favorite":["fishing","baseball"]},{"Name":"hoge","Age":26,"Favorite":["programming"]},{"Name":"fuga","Age":10,"Favorite":["study","programming"]}]
Marshal
の中身はこんな感じです。
値はさっきUnmarshalで使ったものをそのまま使用しました。
戻り値が[]byteとのことなのでいい感じにするためにstringに変換しました。
func Marshal(v interface{}) ([]byte, error)
いい感じに変換されました。
これどうやら遅いらしい
らしいです。
easyjson
そこで出てくるのが、[easyjson](https://godoc.org/github.com/mailru/easyjson)というやつです。詳しくは自分で調べてください。
go getで持ってきます。
go get -u github.com/mailru/easyjson/...
これを使うと構造体ごとにコードを自動生成してReflectionなしで高速で先ほどのMarshalやUnmarshalができるようになります。
まずは適当なファイルを作成します。今回は```easy.go```というファイルを作成しました。
package main
type Human struct {
Name string
Age int
Favorite []string
}
これを作成したら、ターミナルで以下のコマンドを叩きます。
easyjson -all easy.go
そうすると、同じディレクトリの中に新しく```easy_easyjson.go```というファイルが自動で作られます。
中身はこんな感じになってます。
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package main
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 easyjson97766e5aDecodeJsonPracticeEasy(in *jlexer.Lexer, out *Human) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "Name":
out.Name = string(in.String())
case "Age":
out.Age = int(in.Int())
case "Favorite":
if in.IsNull() {
in.Skip()
out.Favorite = nil
} else {
in.Delim('[')
if out.Favorite == nil {
if !in.IsDelim(']') {
out.Favorite = make([]string, 0, 4)
} else {
out.Favorite = []string{}
}
} else {
out.Favorite = (out.Favorite)[:0]
}
for !in.IsDelim(']') {
var v1 string
v1 = string(in.String())
out.Favorite = append(out.Favorite, v1)
in.WantComma()
}
in.Delim(']')
}
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson97766e5aEncodeJsonPracticeEasy(out *jwriter.Writer, in Human) {
out.RawByte('{')
first := true
_ = first
{
const prefix string = ",\"Name\":"
out.RawString(prefix[1:])
out.String(string(in.Name))
}
{
const prefix string = ",\"Age\":"
out.RawString(prefix)
out.Int(int(in.Age))
}
{
const prefix string = ",\"Favorite\":"
out.RawString(prefix)
if in.Favorite == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
out.RawString("null")
} else {
out.RawByte('[')
for v2, v3 := range in.Favorite {
if v2 > 0 {
out.RawByte(',')
}
out.String(string(v3))
}
out.RawByte(']')
}
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v Human) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson97766e5aEncodeJsonPracticeEasy(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Human) MarshalEasyJSON(w *jwriter.Writer) {
easyjson97766e5aEncodeJsonPracticeEasy(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Human) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson97766e5aDecodeJsonPracticeEasy(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Human) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson97766e5aDecodeJsonPracticeEasy(l, v)
}
なんとも便利な〜
これは先ほどのMarshalやUnmarshalと同じように使うことができます。
まずはMarshalから
package main
import (
"fmt"
"json_practice/easy"
"log"
"github.com/mailru/easyjson"
)
func main() {
req := Human{
Name: "takurinton",
Age: 20,
Favorite: []string{
"running",
"baseball",
},
}
h, err := easyjson.Marshal(req)
if err != nil {
log.Fatal(err)
}
res := string(h)
fmt.Println(res)
}
{"Name":"takurinton","Age":20,"Favorite":["running","baseball"]}
こんな感じでうまく変換することができます。同じように使用できるのはいいですねえ。easy.goを構造体ではなくスライスの中に構造体入れるみたいな感じにしてあげればそれもまたいい感じに変換してくれます。
Unmarshalも上と同様に同じ値を使って実装してみたいと思います。
package main
import (
"fmt"
"json_practice/easy"
"log"
"github.com/mailru/easyjson"
)
func main() {
req := Human{
Name: "takurinton",
Age: 20,
Favorite: []string{
"running",
"baseball",
},
}
h, err := easyjson.Marshal(req)
if err != nil {
log.Fatal(err)
}
human := Human{}
if err := easyjson.Unmarshal(h, &human); err != nil {
log.Fatal(err)
}
fmt.Println(human)
}
{takurinton 20 [running baseball]}
こんな感じで出力されます。
結構簡単に実装できますね。
まとめ
これ実はインターンで実装して、めちゃくちゃタイム速くなったんですよね。
標準パッケージのencoding/jsonよりもだいぶ速さが出る上に、安定して高速を出すことができます。
しかし、上で実装してるとわかりますが、特定のstructに対してしか効果を発揮しませんので汎用性には欠けます。使い所を間違えると大変なことになりそうなのできっちり締めるところは締めるみたいな時(?)には使えそう。。。
てな感じで今日は転載記事なのでほぼ文章書いてませんが以上です♪(´ε` )