これは Go2 Advent Calender 2020 の 17 日目の記事です。
Go は encoding/json
という JSON を取り扱うための公式 package を提供しており、
簡単に JSON を取り扱うことができます。
今回は、その package を使って JSON を取り扱う際の代表的なプラクティスをまとめました。
ターゲットとする読者
本記事がターゲットとする読者は以下の通りです。
- 今から Go で JSON をこねこねしようと思っている人
- Go で JSON を扱ったことがあるけど、プラクティス集があると振り返れていいなと思う人
Practice 1: 構造体との Unmarshal/Marshal
以下は一番基本の JSON の decode/encode のスタイルです。
JSON に対応した構造体を定義し、その構造体へ JSON を decode/encode します。
API 名としては、decode = Unmarshal、encode = Marshal として表現されています。
Unmarshal
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Dog struct {
DogBreed string
Name string
Age string
}
jsonBlob := []byte(`{"DogBreed": "Shiba", "Name": "Taro", "Age": "1"}`)
var dog Dog
err := json.Unmarshal(jsonBlob, &dog)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%v\n", dog)
}
$ go build -o a.exe && a.exe
{Siba Taro 1}
Marshal
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Dog struct {
DogBreed string
Name string
Age string
}
dog := Dog{DogBreed: "Shiba", Name: "Taro", Age: "1"}
var jsonBlob []byte
jsonBlob, err := json.Marshal(&dog)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%v\n", string(jsonBlob))
}
$ go build -o a.exe && a.exe
{"DogBreed":"Shiba","Name":"Taro","Age":"1"}
tips
- JSON の key と構造体のメンバ名は一致している必要がある
- JSON の key と異なる名前を付ける場合は、JSON tag を付与する (この記事では説明していない)
- 構造体のメンバは export (CamelCase に) する必要がある
Practice 2: Unmarshal 方法のバリエーション
Practice 1 の方法は定番の方法ではあるものの、構造体を定義したりやや面倒な方法ではあります。
Unmarshal/Marshal は *interface{}
を渡せる仕様となっているため、以下の様に記述することができます。
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
jsonBlob := []byte(`{"DogBreed": "Shiba", "Name": "Taro", "Age": "1"}`)
var dog interface{}
err := json.Unmarshal(jsonBlob, &dog)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%v\n", dog)
}
$ go build -o a.exe && a.exe
map[Age:1 DogBreed:Shiba Name:Taro]
tips
-
*interface{}
を渡した場合、map
型で Unmarshal 結果が返ってくる
Practice 3: 遅延デコード
Practice1、2 のコードでは、一回の encode/decode 処理で JSON を Go のデータオブジェクトへ変換していました。
しかし、それでは「ある JSON の key の値に従って、JSON の decode の仕方を変更したい」場合に上手く decode することができません。
それに対応する方法としては、以下のコードの様に、遅延デコードしたい JSON key を *json.RawMessage
へ一旦保存しておく方法があります。
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Data struct {
Type string
Person json.RawMessage
}
type PersonA struct {
Name string
Age string
}
type PersonB struct {
Name string
Birthday string
}
jsonStr := `[{"Type":"A", "Person":{"Name":"taro","Age":"1"}},{"Type":"B", "Person":{"Name":"Jiro","Birthday":"2020/11/15"}}]`
var data []Data
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
log.Fatal(err)
}
for _, d := range data {
switch d.Type {
case "A":
var p PersonA
if err := json.Unmarshal(d.Person, &p); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", p)
case "B":
var p PersonB
if err := json.Unmarshal(d.Person, &p); err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", p)
default:
log.Fatal("invalid type")
}
}
}
$ go build -o a.exe && a.exe
{Name:taro Age:1}
{Name:Jiro Birthday:2020/11/15}
tips
-
json.RawMessage
型の実体は[]byte
のため、その JSON byte 列に対して再度 Unmarshal をするイメージ
まとめ
- encoding/json で大体のやりたいことは実現できる
- loose に decode する方法についても提供されている
- 逆に strict な key の存在チェックなどについては実装されていないため、やりたい時はサードパーティ等に頼る必要がある