easyjsonを使ってみた
2020-10-21
こんにちは
どうも、 僕 です。
この記事は随分前のインターン期間中に自分のために書いた記事を転載してます。
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 という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で使ったものをそのまま使用しました。
いい感じに変換されました。
# これどうやら遅いらしい
らしいです。
# easyjson
そこで出てくるのが、[easyjson](https://godoc.org/github.com/mailru/easyjson)というやつです。詳しくは自分で調べてください。
go getで持ってきます。
bash
go get -u github.com/mailru/easyjson/...
これを使うと構造体ごとにコードを自動生成してReflectionなしで高速で先ほどのMarshalやUnmarshalができるようになります。
まずは適当なファイルを作成します。今回は
easy.go
というファイルを作成しました。
go
package main
type Human struct {
Name string
Age int
// 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: )
}
{
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() ( {
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 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から
go
package main
import (
"fmt"
"json_practice/easy"
"log"
"github.com/mailru/easyjson"
)
func main() {
req := Human{
Name: "takurinton",
Age: 20,
if err != nil {
log.Fatal(err)
}
res := string(h)
fmt.Println(res)
}
bash:出力
{"Name":"takurinton","Age":20,"Favorite": "running","baseball"
func main() {
req := Human{
Name: "takurinton",
Age: 20,
if err != nil {
log.Fatal(err)
}
human := Human{}
if err := easyjson.Unmarshal(h, &human); err != nil {
log.Fatal(err)
}
fmt.Println(human)
}
bash:出力
{takurinton 20 running baseball