golangのjson parseについてのtips(marshal/decode/json.RawMessage)

Posted by     "roadman" on Thursday, October 3, 2019

TOC

  • golangでjsonをparseする時、いくつか方法があるので、パターン別に自分の使い分けを解説。

jsonテキストをstructの変数に流し込む(json.Unmarshal)

  • jsonテキストをパースする一番よくあるパターン。まあ現実的には同じ関数内で作ったjsonテキストをparseなどしないが…
package main

import (
	"encoding/json"
	"fmt"
)

type jsonData struct {
	Name string `json:"name"`
	Num  int    `json:"num"`
}

func main() {
	jsonStr := `{"name":"Apple","num":3}`
	jsonBytes := []byte(jsonStr)
	var d jsonData
	json.Unmarshal(jsonBytes, &d)
	fmt.Println(d.Name, d.Num)
}

io.Readerから1回でstructの変数に流し込む場合(json.NewDecoder)

  • よくあるパターン。例えばサーバのapiにhttp.Getでリクエストして、Response typeのBody fieldをparseするような場合だ。
  • サンプルとかも多いので解説の必要はないだろう。シンプルに以下でいい。
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
)

type respBody struct {
	Result  int
	Message string
}

func main() {
	resp, err := http.Get("どっかのAPI")
	var data respBody
	err = json.NewDecoder(resp.Body).Decode(&data)
	if err != nil {
		fmt.Println(err.Error())
	}
}
  • このパターンの場合、resp.Bodyio.Readerで読み込みは一度しかできない。

io.Readerから複数回でstructの変数に流し込む場合(json.Unmarshal, json.RawMessage)

  • APIからのresponseをparseする場合に割とあるのがこれ。responseの構造がapiの結果によって変わる場合などだ。200応答と、500応答とでpayloadの構造が違う…とか。
  • この場合、一部のfieldだけparseしてから残りを処理分岐したい。
  • io.Readerのままだと複数回読み込みはできないので、[]byteなどでデータを持っておく必要があるが、加えて一度parseしたfieldを再度parseするというのはどうにもダサい。
  • 以下のようにparseを遅らせたい項目をjson.RawMessageにしておくと、後からjson.Unmarshalを行うことができる。
  • json.RawMessage[]byteを型定義したものなので、そのままjson.Unmarshalで処理可能だ。
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type respBody struct {
	Result  string          `json:"result"`
	Payload json.RawMessage `json:"payload"`
}

type respPayloadOK struct {
	Message string `json:"message"`
}

type respPayloadErr struct {
	ErrCode    int    `json:"errCode"`
	ErrMessage string `json:"errMessage"`
}

func main() {
	resp, err := http.Get("どっかのAPI")

	bodyBytes, err := ioutil.ReadAll(resp.Body)

	var data respBody

	if err = json.Unmarshal(bodyBytes, &data); err != nil {
		fmt.Println(err.Error())
		return
	}

	if data.Result != "ok" {
		var payloadErr respPayloadErr
		if err = json.Unmarshal(data.Payload, &payloadErr); err != nil {
			fmt.Println(err.Error())
			return
		}
		fmt.Println(payloadErr.ErrCode, payloadErr.ErrMessage)
		return
	}

	var payload respPayloadOK
	if err = json.Unmarshal(data.Payload, &payload); err != nil {
		fmt.Println(err.Error())
		return
	}

	fmt.Println(payload.Message)
}