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)
    }