12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go 2Advent Calendar 2020

Day 17

Go で JSON を取り扱う際のプラクティス集

Last updated at Posted at 2020-12-16

これは 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 の存在チェックなどについては実装されていないため、やりたい時はサードパーティ等に頼る必要がある

References

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?