これからgoでjson形式のデータをstructにmappingする際にはstreamを使うことを決意した話です。
経緯
下記のエントリーに触発されました。
goでjsonをstructにmappingすることをはじめ、 io.Reader
のデータを扱うときは一度 []byte
型に変換してから諸々の処理を行うというのが一般的な実装方法です。
一般的なjsonのmapping
APIのレスポンスなど、goにおいて io.Reader
型を受け取り、[]byte
に変換してからjsonのデータ構造をオブジェクトにmappingするときの一般的なコードは下のような形式になるかと思います。
client := http.Client{} resp, err := client.Get(url) if err != nil { fmt.Printf("Error: get error. err: %v", err) return nil, err } body := resp.Body() defer body.Close() b, err := ioutil.ReadAll(body) if err != nil { panic(err) } var data SomeData if err := json.Unmarshal(b, &data); err != nil { panic(err) }
一般的な上記のコードですが、byte型に変換する上で上述のエントリに記載されているようなメモリ効率が悪い点に加えて、
の計3回エラーハンドリングの処理を書かなければならず、コードがその分長くなるのが個人的にはちょっと冗長になっていると感じます。
streamを使う
[]byte
型に変換して json.Unmarshal
する方法を使わずstreamを利用したコードは以下
client := http.Client{} resp, err := client.Get(url) if err != nil { fmt.Printf("Error: get error. err: %v", err) return nil, err } defer resp.Body.Close() var data SomeData if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { panic(err) }
[]byte
型に変換せずに json.NewDecoder(io.Reader)
にそのまま io.Reader
型のbodyを渡して、decodeします。
streamを利用すると、 []byte
型に変換する箇所が丸々なくなるので、ここでエラーハンドリングの回数が一回減って、コードも見通しがよくなります。
cf. エントリ内で参照されている
の中で言及されていますが、
So a better rule of thumb is this:
Use json.Decoder if your data is coming from an io.Reader stream, or you need to decode multiple values from a stream of data.
Use json.Unmarshal if you already have the JSON data in memory.For the case of reading from an HTTP request, I'd pick json.Decoder since you're obviously reading from a stream.
- 単一のio.Readerからデータを取得した場合か単一streamにおいて複数の値をDecodeする場合 ->
json.Decoder
を使うべき - すでにmemoryにjsonのデータを保持している場合 ->
json.Unmarshal
を使うべき
とされています。
APIからのレスポンスを取得するようなユースケースにおいては最初からmemoryにjsonのデータが保持されているわけではないので、 json.Decoder
の方が適しているということですね。
実際に書き換えてみた
以前作った下記のリポジトリでxmlをDecodeする箇所をstreamで書き換えてみました。
https://github.com/emahiro/golang-hatena-client/commit/c1ffbc8bed30a2f526d19a616dc01d360889c78b のコミットで書き換えてます。
まとめ
これからは、io.Reader
をinterfaceとして持つ io.ReadCloser
型のBodyなど、streamを使える場合は積極的にstreamのまま使っていこうと思います。
また、io.Reader
型を生成して、使い回すというテクニックも覚えていこうと思います。
例えば....
zip.NewReader
やgzip.NewReader
のようなNewReader
のインターフェースを持つ場合は積極的に、io.Reader
型に変換するなど