blog.takurinton.dev

easyjsonを使ってみた

2020-10-21

こんにちは

どうも、 です。

この記事は随分前のインターン期間中に自分のために書いた記事を転載してます。

GoでJSON使う時ってだいぶめんどくさいんですよね。まあ型による安心感がバケモンなのでやった方がいいんですけど。

GoでJSONを捌く時はstructを使用します。

クラスとかはないのでこれでいきます。

Unmarshal

早速やってみます。

例えばこんな感じのjsonがくるとします。

go
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で扱える形に変換したい時はこんな感じで書いてあげます。

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)
	}
}
bash
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のスライス、第二引数はインターフェイスを渡してあげます。上のプログラムでもしっかり値を渡すことができています。

go
func Unmarshal(data []byte, v interface{}) error

Marshal

逆もできます。

go
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)
}
bash:出力
[{"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

Favorite

// 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,

Favorite:

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,

Favorite:

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