GoでAPIサーバーを立ち上げるケースは多いと思います。またクライアントとのデータのやり取りで用いられる形式の多くはJSONです。ですのでGoでJSONのレスポンスを返すAPIを実装することは多くなります。今回は、GoでJSONを用いたAPIを実装する方法について解説します。
GoでJSONを扱う方法
Goではencoding/jsonを用いてJSONを構造体にデコードしてJSONを扱うことができます。反対に構造体をエンコードしてJSONを作成することができます。デコードとエンコードそれぞれについて説明します。
GoでJSONをデコードする(JSON => 構造体)
GoでJSONを受け取りそれを構造体に変換してみましょう。今回は説明の簡略のため直接JSONを読み込んで使用します。使用するJSONは以下のようなものです。
タイトル:sample.json
[ { "id": 1, "name": "Micheal" }, { "id": 2, "name": "Nancy" }, { "id": 3, "name": "Jon" }, { "id": 4, "name": "Rachel" } ]
これをgoファイルで読み取り構造体にしてみましょう。
GoでのJSONのエンコードにはjson.Unmarhsalメソッドを使用します。
メソッド名 | 引数 | 戻り値 |
json.Unmarshar | JSONのデータ([]byte型), JSONをデコードして保存する変数(任意の型) | err error型 |
json.Unmarshalメソッドを用いて先ほどのJSONデータを読み込みgoで出力してみましょう。
タイトル:main.go
package main import ( "encoding/json" "fmt" "io/ioutil" ) //Person sample.jsonのデコード用の構造体 type Person struct { ID int `json:"id"` Name string `json:"name"` Sex string `json:"sex"` } func main() { bytes, err := ioutil.ReadFile("sample.json") if err != nil { panic(err.Error()) } var persons []Person if err := json.Unmarshal(bytes, &persons); err != nil { panic(err.Error()) } for _, v := range persons { fmt.Printf("%d:%s\n", v.ID, v.Name) } }
簡単に解説していきます。
タイトル:main.go
//Person sample.jsonのデコード用の構造体 type Person struct { ID int `json:"id"` Name string `json:"name"` Sex string `json:"sex"` }
まずはデコードして作成する構造体を定義しておきます。この際に`json:"jsonファイルのフィールド名"`
を追記する必要があります。構造体のフィールドは任意ですのでJSONのフィールド名と一致させる必要はありません。Sexフィールドについては後ほど使用します。
タイトル:main.go
bytes, err := ioutil.ReadFile("sample.json") if err != nil { panic(err.Error()) }
main関数内ではまず、jsonファイルの読み込みを行います。エラーハンドリングもきちんとしておきましょう。
タイトル:main.go
var persons []Person if err := json.Unmarshal(bytes, &persons); err != nil { panic(err.Error()) }
続いてjson.Unmarshalメソッドを用いて先ほど作成した構造体にデコードしたjsonファイルのデータを構造体にします。
タイトル:main.go
for _, v := range persons { fmt.Printf("%d:%s\n", v.ID, v.Name) }
最後に構造体の中身を出力しています。
タイトル:ターミナル
$ go run main.go
GoでJSONをエンコードする(構造体 => JSON)
続いてGoでの構造体をJSON形式にしてレスポンスを返す処理について説明します。JSONのエンコードにはjson.Marshalメソッドを用います。
メソッド名 | 引数 | 戻り値 |
json.Marshal | json形式にエンコードする変数(任意の型) | json形式のbyte列([]byte型), エラー(error型) |
json.Marshalメソッドを用いて先ほどJSONをデコードして作成した構造体に少し手を加えたものをjson形式のbyte列に変換してみましょう。
タイトル:main.go
func main() { bytes, err := ioutil.ReadFile("sample.json") if err != nil { panic(err.Error()) } var persons []Person if err := json.Unmarshal(bytes, &persons); err != nil { panic(err.Error()) } for i, v := range persons { if v.Name == "Micheal" || v.Name == "Jon" { persons[i].Sex = "male" } else { persons[i].Sex = "female" } } js, err := json.Marshal(persons) if err != nil { panic(err.Error()) } fmt.Printf("%b", js) }
for文のループ処理の部分でMicahealとJonについてはmaleをそれ以外のメンバーにはfemaleをSexフィールドを指定して代入していきます。
タイトル:main.go
js, err := json.Marshal(persons) if err != nil { panic(err.Error()) }
この処理がなされたpersonsについてjson.MarshalメソッドでJSON形式にエンコードし、エンコードされたbyte列を出力しています。
タイトル:ターミナル
$ go run main.go
このように1と0で表されたものが表示されていれば正常に変換されています。
GoでJSONをレスポンスに含める
最後に、先ほどjson形式にエンコードしたbyte列をhttpリクエストに対するレスポンスに書き込む処理について説明します。なお、この処理にはGoでローカルサーバーを立ち上げてURLのリクエストに対するハンドラを作成できることを前提知識としています。
ここで取り上げたコードについてはcode-databaseのgithubリポジトリにも上げてありますので手元で確認したい方は是非参考にしてみてください。
json形式のbyte列をレスポンスに加えるにはhttp.ResponseWriter型のWriteメソッドを用いますが、その前にHeader型のSetメソッドでレスポンスの形式について指定しておく必要があります。
メソッド名 | 引数 | 戻り値 |
(h Header).Set | ヘッダーのキー(string型), ヘッダーの内容(string型) | なし |
(w http.ResponseWriter).Write | レスポンスに書き込む内容([]byte型) | レスポンスに失敗したときのステータスコード(int型), エラー(error型) |
これらのメソッドを使って、/api/persons/というリクエストに対するレスポンスに含めてみましょう。コードは以下のようになります。
タイトル:main.go
package main import ( "encoding/json" "io/ioutil" "net/http" ) //Person sample.jsonのデコード用の構造体 type Person struct { ID int `json:"id"` Name string `json:"name"` Sex string `json:"sex"` } func personsHandler(w http.ResponseWriter, r *http.Request) { bytes, err := ioutil.ReadFile("sample.json") if err != nil { panic(err.Error()) } var persons []Person if err := json.Unmarshal(bytes, &persons); err != nil { panic(err.Error()) } for i, v := range persons { if v.Name == "Micheal" || v.Name == "Jon" { persons[i].Sex = "male" } else { persons[i].Sex = "female" } } js, err := json.Marshal(persons) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(js) } func main() { http.HandleFunc("/api/persons/", personsHandler) http.ListenAndServe(":8000", nil) }
これについても簡単に解説します。
タイトル:main.go
func main() { http.HandleFunc("/api/persons/", personsHandler) http.ListenAndServe(":8000", nil) }
main関数内では/api/persons/のリクエストに対するハンドラの指定とサーバーの立ち上げを行っています。
タイトル:main.go
func personsHandler(w http.ResponseWriter, r *http.Request) { // 省略 json, err := json.Marshal(persons) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(json) }
ハンドラ関数のpersonsHandler関数内ではpersonsをエンコードしてw.Header().Set("Content-Type", "application/json")
では、wからヘッダーを取得し(Header()
)、そのヘッダーに対して先ほど紹介したSetメソッドでContent-Typeとしてapplication/jsonを指定しています。さらに、w.Write(json)
でレスポンスにjsonを書き込みます。
動作確認してみましょう。
タイトル:ターミナル
$ go run main.go
サーバを立ち上げてhttp://localhost:8000/api/persons/にアクセスしてみます。以下のようにjsonの中身が表示されていれば正常です。
Google Developper ToolのNetworkからリクエストとレスポンスを確認してもきちんとContent-Type: application/json
がレスポンスに指定されていることがわかります。
この記事のまとめ
本記事ではGolangでJSONのデコード、エンコード、JSONレスポンスへの記述方法について解説しました。最後に記事の要点についてまとめておきます。
- encoding/jsonライブラリを用いてJSON形式のバイト列のエンコードとデコードができる
- デコードすることでJSON形式のバイト列を構造体にjsonを置き換えることができる
- エンコードすることで構造体をJSON形式のバイト列に変換できる
- 構造体の宣言には``でjsonでのフィールド名を指定するする必要がある
- (h Header).Setメソッドでレスポンスのヘッダー情報を書き換えられる
- (w http.ResponseWriter).Writeメソッドでレスポンスに書き込みができる
皆さんもAPIサーバー立ち上げてみましょう。SPAの簡単な構築については「GolangとVue.jsとChart.jsで小さなSPAアプリを作る」も参考にしてみてください。