Go言語は、シンプルな関数を組み合わせてプログラミングをすることが多く、また、関数毎にエラーチェックが発生するので、全般的にコードが長くなりがちです。
ちょっとした事をしたい時でも、コードをそれなりに書く必要があるのですが、毎回ゼロから書くのは面倒なので、よくやる処理を自分コピペ用にメモしておこうと思います。
ここでは、Go言語で、JSONを読み書きする方法の自分用メモを記載しました。
構造体の要素名は大文字
JSONを扱う際、構造体からJSONに変換したり、JSONから構造体に変換したり、構造体を介してやり取りすることが多いです。
Go言語では、構造体の要素名が大文字で始まらないと、他のパッケージにその構造体の要素が公開されません。
なので、json
パッケージを使ってJSONを処理する際、やり取りする構造体の要素名は、大文字で始まらないといけません。
しかし、JSONデータは、必ずしも要素名が大文字で始まってるとは限りません。
そこで、構造体の要素名は大文字で始まるようにしておきながら、それがJSONのどの要素名を示すかを、構造体のタグで示すようにします。
type colStruct struct { Name string `json: "name"` Count int `json: "count"` }
JSON読み込み(構造体)
データを受け取る構造体を用意して、json.Unmarshal()
に、JSONデータと構造体のポインターを渡すと、構造体にデータが格納されます。
data := `{"x":123, "y":"ABC"}` var a struct { X int `json:"x"` Y string `json:"y"` } err := json.Unmarshal([]byte(data), &a) if err != nil { return err } fmt.Printf("%+v\n", a) // {X:123 Y:ABC}
JSON読み込み(インターフェース)
JSONデータの内容が未知の場合、構造体ではなく、インターフェースのポインターを渡すことにより、任意のJSONデータをインターフェースで取得できます。
インターフェースからデータを取り出すには、型アサーションを使用します。
取り出したデータもインターフェースなので、入れ子になったデータを取り出すには、型アサーションを繰り返していくことになります。
data := `{"x":123, "y":"ABC", "z":{"i":123, "j":456}}` var a interface{} err := json.Unmarshal([]byte(data), &a) if err != nil { return err } fmt.Printf("%#v\n", a) // map[string]interface {}{"Z":map[string]interface {}{"a":123, "b":456}, "x":123, "y":"ABC"} b := a.(map[string]interface{})["z"] z := b.(map[string]interface{}) fmt.Println(z["i"]) // 123 fmt.Println(z["j"]) // ABC
JSON出力(構造体)
json.Marshal()
に出力したい構造体を渡すと、JSONにしてくれます。
a := struct { X int `json:"x"` Y string `json:"y"` }{ X: 123, Y: "ABC", } json, err := json.Marshal(a) if err != nil { return err } fmt.Printf("%+v\n", string(json)) // {"x":123,"y":"ABC"}
JSON出力(スライス・マップ・インターフェース)
json.Marshal()
は内部的には全てインターフェースで処理しているので、構造体に限らず、スライス・マップ・インターフェースがごちゃまぜになったデータでも、
良しなにJSONにしてくれます。
z := struct { I int `json:"i"` J int `json:"j"` }{ I: 123, J: 456, } a := map[string]interface{}{ "x": 123, "y": "ABC", "z": z, } json, err := json.Marshal(a) if err != nil { return err } fmt.Printf("%+v\n", string(json)) // {"x":123,"y":"ABC","z":{"i":123,"j":456}}
ストリーム処理
json
はio.Reader
・io.Writer
に直接読み書きできるので、一旦メモリにデータを取り込む必要はありません。
ストリームJSON読み込み
var a struct { X int `json:"x"` Y string `json:"y"` } f, err := os.Open("data_input.json") // {"x":123 "y":"ABC"} if err != nil { return err } defer f.Close() err = json.NewDecoder(f).Decode(&a) if err != nil { return err } fmt.Printf("%+v\n", a) // {X:123 Y:ABC}
ストリームJSON出力
a := map[string]interface{}{ "x": 123, "y": "ABC", } f, err := os.Create("data_output.json") if err != nil { return err } defer f.Close() err = json.NewEncoder(f).Encode(a) if err != nil { return err }
感想など
記事を入力して読み直してみたのですが、エラー処理コードが思考を中断させるため、コードの可読性が良くないですね。
Go言語には「例外」がないので、エラー処理は必須になり省略できないので、Go言語にも「例外」があった方がいいみたいな話を聞いたりします。
Go言語は、本当は行末にセミコロンが必要な言語なのですが、書かなくてもコンパイラが良しなに補完してくれます。
そのように、Go言語には、言語仕様ではなく、コンパイラ解釈で言語を書きやすく、読みやすくするといったことがあります。
なので、Go言語に「例外」を導入しなくてもいいので
「関数の戻り値をerr
変数で受けると、コンパイラが自動で次行にif err != nil { return err }
を挿入する」
みたいなコーディングが楽になる機能が入るといいのになぁ。