質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Q&A

解決済

2回答

400閲覧

golangにおけるjsonデコードについて

mats0228

総合スコア219

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

0グッド

0クリップ

投稿2018/06/29 12:31

golangにおけるjsonデコードについて

前提

  • json decodeまわりをgolangで書いており、質問です。

  • io.Readerのポインタ位置あたりが理解しきれていないのが問題だと思っています。

  • 前提となるAPIレスポンス(JSON形式)は下記の通りです。

// 異常時:エラーメッセージだけ返却される { “Message”:{ “msg struct”: ~~, ~~ } } // 正常時:body structが返却される { “Message”:{ “messageType”: ~~, ~~ }, “ResponseBody”:{ “body strcut”: ~~, ~~ } }

問題

  • こちらを参考に、ネストしたstructを用意してdecodeしています。
//異常時かどうかチェックするためのstruct type HogeMessages struct { Message []struct { MessageType string `json:"messageType"` ~~ } `json:"Message"` } //正常時のデコード用 type HogeFull struct { Message []struct { MessageType string `json:"messageType"` ~~ } `json:"Message"` Body []struct { someBody string `json:”someBody”` ~~ } `json:”Body”` }
  • デコード処理をこのように書いています。
func NewDecodeJson(body io.ReadSeeker) (*HogeFull, error) { var hogeFull HogeFull // Messageをチェック。エラーの場合は、HogeFullのパースができない if !orderLists.IsExistBody(body) { return &HogeFull{}, fmt.Errorf(“not exist”) } //body.Seeker(0,0) //★これを追加すると、エラーが解消する。 //ただし、引数が`io.ReadSeeker`でないと利用できないので、resp.Bodyを引数として渡せない if err := json.NewDecoder(body).Decode(&hogeFull); err != nil { return &HogeFull{}, err } return &HogeFull, nil }
  • 実行結果として、下記のエラーが発生しています。
2018/06/29 21:20:49 [TEST] hogeFull=&{[] [] {0 0 0}} panic: runtime error: index out of range [recovered] panic: runtime error: index out of range ~~~
  • 上記サンプルの「★」を追加して、offsetを先頭に戻すと成功するようです。ただし、reso.Bodyは、io.ReadCloserのため、上記に渡せなくなってします。

ちなみに

  • 下記のようにTeeReader()も使ってみましたが、同様にエラーがでてしまいます。
func NewDecodeJson(body io.Reader) (*HogeFull, error) { var hogeFull HogeFull bufForExam := &bytes.Buffer{} teeBody := io.TeeReader(body, bufForExam) //offsetを変更しないようにcopy // Messageをチェック。エラーの場合は、HogeFullのパースができない if !orderLists.IsExistBody(bufForExam) { return &HogeFull{}, fmt.Errorf(“not exist”) } if err := json.NewDecoder(teeBody).Decode(&hogeFull); err != nil { return &HogeFull{}, err } return &HogeFull, nil }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

細かいほうから指摘します

  • エラーが出たのであればそのメッセージを貼ってもらったほうが回答しやすいです。
  • io.TeeReaderはteeBodyを読むことで読んだ内容と同様の内容がbufForExamに投入される仕掛けです。
  • なので上記のTeeReaderを使ったサンプルでは正しく読めません。
  • Seekerでなければ一度読んだ内容を再度読む方法がないのは指摘の通りです。
  • HogeMessagesとHogeFullは分離する必要がない。正常か異常かのどちらの結果であってもHogeFull構造でデコードできます。
  • つまり、受け取った結果のBodyの長さがゼロなら異常と判定すればよい。

JSONの定義を決める際には一つのAPI機能に一意な構造を一般には規定します。
条件別に構造を変化させるなんてことはわざわざ行いません。
存在しないように見えるフィールドは単に省略されているだけの場合が多いのです。

追記:

サイズをReaderから取得したうえでデコーダーに渡したい場合の実装例です。
io.TeeReaderとio.Copyを組み合わせます。

https://play.golang.org/p/rHDT0SHUnwp

go

1package main 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "log" 10) 11 12var buff = bytes.NewBufferString(`{"messages":[],"body":[]}`) 13 14func GetSizeFromReader(r io.Reader) (sz int64, reader io.Reader, err error) { 15 b := bytes.NewBuffer(nil) 16 n, err := io.Copy(ioutil.Discard, io.TeeReader(r, b)) 17 if err != nil { 18 return 0, nil, err 19 } 20 return n, b, nil 21} 22 23func main() { 24 sz, r, err := GetSizeFromReader(buff) 25 if err != nil { 26 log.Fatalln(err) 27 } 28 if sz > 0 { 29 var v interface{} 30 if err := json.NewDecoder(r).Decode(&v); err != nil { 31 log.Fatalln(err) 32 } 33 fmt.Println(v) 34 } 35}

httpのハンドリングであればContent-Lengthヘッダーをパースするのも一つの手かと。

投稿2018/07/02 09:00

編集2018/07/03 02:49
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mats0228

2018/07/02 11:55

回答ありがとうございます!!一つ目のご回答でエラー発生原因がしっくりきました。 下記を踏まえて、設計レベルで見直します。 * Seekerでなければ一度読んだ内容を再度読む方法がない * JSONの定義を決める際には一つのAPI機能に一意な構造 1点、Readerの使い勝手と言う点で似たような質問なのですが、 `io.Copy()`についても、渡された`io.Reader`を一度読んでしまうので、下記の関数をつかうと`targetIO`も読み込んでしまう認識であっていますでしょうか? (下記(=io.Readerのサイズ取得)を実現する一般的な方法があれば、お聞きしたいです。) ``` // calculateIOSize :io.Readerからサイズを算出する func calculateIOSize(targetIO io.Reader) int64 { buf := &bytes.Buffer{} size, err := io.Copy(buf, targetIO) if err != nil { log.Println("[ERROR] calculateIOSize() ", err) return 0 } // log.Print("[DEBUG] reqBody size", strconv.FormatInt(size, 10), buf) return size } ```
nobonobo

2018/07/02 23:08

あー。なるほどReaderひとつからコンテンツサイズ取得とコンテンツのデコードの2つの処理を行いたいのですね。 それならio.TeeReaderという選択で合ってます。 その正しい使い方がわかれば大丈夫。 あとでサンプルを書きます。
mats0228

2018/07/03 03:47

サンプル記載ありがとうございました。 関数のもどりとしてio.TeeReaderでteeしたReaderを返却してあげるのがポイントですね! io.Readerについても理解が深まりました。ありがとうございました。
guest

0

calculateIOSize の用途補足としては、手元で作ったリクエストボディのサイズを、clientオブジェクトにセットすることを想定しています。

投稿2018/07/02 12:10

mats0228

総合スコア219

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問