1章の表1で紹介したように、
encoding/jsonパッケージ
JSONを扱うためにはencoding/Marshal()
と、Unmarshal()
が提供されています。
構造体からJSONへの変換
まずは次のような構造体を用意します。
type Person struct {
ID int
Name string
Email string
Age int
Address string
memo string
}
この構造体にデータを代入し、json.
に渡すだけで、[]byte
を生成できます。
func main() {
person := &Person{
ID: 1,
Name: "Gopher",
Email: "[email protected]",
Age: 5,
Address: "",
memo: "golang lover",
}
b, err := json.Marshal(person)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b)) // 文字列に変換
}
出力されたJSONは、
{
"ID": 1,
"Name": "Gopher",
"Email": "[email protected]",
"Age": 5,
"Address": ""
}
タグ付け
変換されたJSONを見てみると、
encoding/
`json:"name"` // nameというキーで格納する
`json:"-"` // JSONに格納しない
`json:",omitempty"` // 値が空なら無視
`json:",string"` // 値をJSONとして格納
タグは次のように、
type Person struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"-"`
Age int `json:"age"`
Address string `json:"address,omitempty"`
memo string
}
出力結果は次のように変わります。
fmt.Println(string(b))
// {"id":1,"name":"Gopher","age":5}
JSONから構造体への変換
逆にJSONの文字列からデータをマップした構造体を生成するには、json.
を使用します。格納するJSONと、
func main() {
var person Person
b := []byte(`{"id":1,"name":"Gopher","age":5}`)
err := json.Unmarshal(b, &person)
if err != nil {
log.Fatal(err)
}
fmt.Println(person) // {1 Gopher 5 }
}
os、ioパッケージ
次に、
ファイルの生成
まずosパッケージを用いてファイルを作成してみましょう。os.
関数にファイル名を渡すと、*os.
構造体へのポインタが取得できます。このとき第二戻り値にエラーが返るため最初にエラー処理をします。
package main
import (
"log"
"os"
)
func main() {
// ファイルを生成
file, err := os.Create("./file.txt")
if err != nil { // エラー処理
log.Fatal(err)
}
// プログラムが終わったらファイルを閉じる
defer file.Close()
}
*os.
は、io.
というインタフェース型であり、Read()
、Write()
、Close()
の3つのメソッドを実装していることを意味します。開いたファイルは使い終わったら閉じる必要があるので、defer
を用いてClose()
をmain()
の終わりで実行するようにします。
ファイルへの書き込み
続いてファイルにデータを書き込んでみましょう。
先ほど取得した*os.
は、io.
インタフェースを実装していました。これは次のように定義されています。
type Writer interface {
Write(p []byte) (n int, err error)
}
[]byte
を引数として渡すと、
これを利用して、hello world
を書き込むには次のようにします。
func main() {
// ファイルを生成
file, err := os.Create("./file.txt")
if err != nil { // エラー処理
log.Fatal(err)
}
// プログラムが終わったらファイルを閉じる
defer file.Close()
// 書き込むデータを[]byteで用意する
message := []byte("hello world\n")
// Write()を用いて書き込む
_, err = file.Write(message)
if err != nil { // エラー処理
log.Fatal(err)
}
}
ここでは、Write()
の第一戻り値である書き込まれたバイト数は無視していますが、
実行し、
$ go run write.go
$ cat file.txt
hello world
また、WriteString()
を用いると、[]byte
に変換する必要がなくなります。
_, err = file.WriteString("hello world\n")
書き込む対象のio.
がWriteString()
のようなメソッドを実装していない場合は、fmt.
を用いると、[]byte
を経由せずio.
に対して文字列を直接書き込むことができます。
_, err = fmt.Fprint(file, "hello world\n")
このようにデータを書き込む方法はいくつかありますが、io.
インタフェース型であることを利用している点を意識するとよいでしょう。
ファイルからの読み出し
次に書き込んだデータを読み出してみます。すでにあるファイルを開く場合はos.
を用います。
func main() {
// ファイルを開く
file, err := os.Open("./file.txt")
if err != nil { // エラー処理
log.Fatal(err)
}
// プログラムが終わったらファイルを閉じる
defer file.Close()
}
ファイルの読み出しにはio.
インタフェースを用います。
type Reader interface {
Read(p []byte) (n int, err error)
}
Read()
は、hello world\n
という12byteのデータを読み出すため、
func main() {
// ファイルを開く
file, err := os.Open("./file.txt")
if err != nil { // エラー処理
log.Fatal(err)
}
// プログラムが終わったらファイルを閉じる
defer file.Close()
// 12byte格納可能なスライスを用意する
message := make([]byte, 12)
// ファイル内のデータをスライスに読み出す
_, err = file.Read(message)
if err != nil { // エラー処理
log.Fatal(err)
}
// 文字列にして表示
fmt.Print(string(message))
}
以上が、*os.
が実装しているio.
インタフェースを用いた最も基本的なファイル操作です。これはファイル操作以外に、
JSONのEncoder/Decoder経由の保存
ここまでの2つを組み合わせると、[]byte
でやりとりすればよいことは容易に想像できるでしょう。しかし、io.
を扱うAPIも用意されているため、io.
であるファイルを扱うにはこちらを用いることができます。
まず、json.
を用いてJSONにデータを変換しつつ、io.
経由でファイルに書き込んでみましょう。
func main() {
person := &Person{
ID: 1,
Name: "Gopher",
Email: "[email protected]",
Age: 5,
Address: "",
memo: "golang lover",
}
// ファイルを開く
file, err := os.Create("./person.json")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// エンコーダの取得
encoder := json.NewEncoder(file)
// JSONエンコードしたデータの書き込み
err = encoder.Encode(person)
if err != nil {
log.Fatal(err)
}
}
JSONへの変換結果を[]byte
として受け取ることなく、*os.
に書き込んでいることがわかると思います。
同様に、json.
を用いてファイル内のJSONデータを読み出し、Person
にデコードしてみましょう。
func main() {
// ファイルを開く
file, err := os.Open("./person.json")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// データを書き込む変数
var person Person
// デコーダの取得
decoder := json.NewDecoder(file)
// JSONデコードしたデータの書き込み
err = decoder.Decode(&person)
if err != nil {
log.Fatal(err)
}
// 読み出した結果の表示
fmt.Println(person)
}
こちらも、[]byte
として受け取ることなく、Person
型の変数に格納しています。
このように、io.
、io.
を中心として設計されたAPIが多くあります。この点を意識してドキュメントを見ると、
io/ioutilパッケージ
ファイルの操作は、
全体の読み出し
ioutil.
は、io.
を渡すと、[]byte
型で返します。先ほどの*os.
の読み出しでは直接Read()
を呼んでいたため十分な長さの[]byte
を用意する必要がありましたが、
// ファイルの中身をすべて読み出す
file, _ := os.Open("./file.txt")
message, err := ioutil.ReadAll(file)
これはio.
を実装したすべての型で使用できるため、
ファイルの読み書き
ファイル操作に特化したメソッドも用意されています。
ioutil.
は、[]byte
として読み出します。
// ファイルの中身をすべて読み出す
message, err := ioutil.ReadFile("./file.txt")
iotuil.
は、[]byte
型のデータを書き込みます。第三引数にはファイルのパーミッションを8進数で指定します。
message := []byte("hello world\n")
err := ioutil.WriteFile("./file.txt", message, 0666)
これらを用いて先ほどの操作を書き直すと、
package main
import (
"fmt"
"io/ioutil"
"log"
)
func main() {
// ファイルにメッセージを書き込む
hello := []byte("hello world\n")
err := ioutil.WriteFile("./file.txt", hello, 0666)
if err != nil { // エラー処理
log.Fatal(err)
}
// ファイルの中身を全て読み出す
message, err := ioutil.ReadFile("./file.txt")
if err != nil { // エラー処理
log.Fatal(err)
}
fmt.Print(string(message)) // 文字列にして表示
}
特にファイルI/
net/httpパッケージ
net/
hello world サーバ
まず、
package main
import (
"fmt"
"net/http"
)
func IndexHandler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "hello world")
}
func main() {
http.HandleFunc("/", IndexHandler)
http.ListenAndServe(":3000", nil)
}
ここでは、http.
でルーティングの設定をします。http.
は次のような定義になっています。
func HandleFunc(pattern string,
handler func(ResponseWriter, *Request))
第一引数はパスのパターンで、/
)
第二引数は2つの引数を受け取る関数になっており、IndexHandler
で実装しています。Request
にはリクエストの情報が入っており、ResponseWriter
に書き込むことでレスポンスを返せます。ResponseWriter
は名前のとおりio.
なので、fmt.
を用いて文字列を書き込んでいます。
最後にmain()
では、http.
にポートを指定してサーバを起動しています。第二引数は今回は使わないためnil
を指定します。
このプログラムを実行し、http://
にアクセスして、hello world
が表示されれば成功です
$ go run server.go

JSON/HTMLサーバ
ここではPOSTで送信されたJSONデータをファイルに保存し、
このサーバは、
type Person struct {
ID int `json:"id"`
Name string `json:"name"`
}
サーバは、
POST
処理はPersonHandler
に実装し、/persons
のパスに対して登録します。ここではPOSTリクエストを処理するため、http.
の値で分岐し、
処理が成功した場合はレスポンスとして201 CREATED
を返すため、ResponseWriter.
にnet/
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type Person struct {
ID int `json:"id"`
Name string `json:"name"`
}
func IndexHandler(w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "hello world")
}
func PersonHandler(w http.ResponseWriter,
r *http.Request) {
defer r.Body.Close() // 処理の最後にBodyを閉じる
if r.Method == "POST" {
// リクエストボディをJSONに変換
var person Person
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&person)
if err != nil { // エラー処理
log.Fatal(err)
}
// ファイル名を {id}.txtとする
filename := fmt.Sprintf("%d.txt", person.ID)
file, err := os.Create(filename) // ファイルを生成
if err != nil {
log.Fatal(err)
}
defer file.Close()
// ファイルにNameを書き込む
_, err = file.WriteString(person.Name)
if err != nil {
log.Fatal(err)
}
// レスポンスとしてステータスコード201を送信
w.WriteHeader(http.StatusCreated)
}
}
func main() {
http.HandleFunc("/", IndexHandler)
http.HandleFunc("/persons", PersonHandler)
http.ListenAndServe(":3000", nil)
}
サーバを起動したら、
$ curl http://localhost:3000/persons -d '{"id":1,"name":"gopher"}'
idを1としたため、gopher
が格納されているはずです。
$ cat 1.txt
gopher
GET
GETが来た場合は、
クエリパラメータはResponseWriter.
から取得できます。この値は文字列であるため、Atoi()
を用います。
// パラメータを取得
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
log.Fatal(err)
}
ここでは、id
に対するファイルを開き、
html/templateパッケージ
Goは、
2つの違いは、
テンプレートの作成
テンプレートはJinja2というテンプレートエンジンと似た記法で記述します。データを埋め込むには{{ }}
で値をくくり、index.
に作成します。
<!DOCTYPE html>
<title>person</title>
<h1>{{ .ID }} : {{ .Name }}</h1>
テンプレートのコンパイルはParseFiles()
という関数を使います。
t, err := template.ParseFiles("index.html")
ParseFiles()
は戻り値としてエラーを一緒に返しますが、Must()
を一緒に用いるとエラー時に戻り値ではなくパニックを発生します。一度コンパイルが通ることを確認したテンプレートであれば、Must()
を合わせて利用することがよくあります。
var t = template.Must(template.ParseFiles("index.html"))
テンプレートへの値の埋め込み
コンパイルしたテンプレートに実際に値を埋め込むには、Execute()
を用います。ここでは第二引数に渡したperson
がテンプレートに適用され、{{ .ID }}
の部分にはperson.
の値が適用されます。第一引数はio.
を渡すと、ResponseWriter
を直接指定します。
// テンプレートのコンパイル
var t = template.Must(template.ParseFiles("index.html"))
func PersonHandler(w http.ResponseWriter,
r *http.Request) {
defer r.Body.Close() // 処理の最後にBodyを閉じる
if r.Method == "POST" {
// リクエストボディをJSONに変換
var person Person
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&person)
if err != nil { // エラー処理
log.Fatal(err)
}
// ファイル名を{id}.txtとする
filename := fmt.Sprintf("%d.txt", person.ID)
file, err := os.Create(filename) // ファイルを生成
if err != nil {
log.Fatal(err)
}
defer file.Close()
// ファイルにNameを書き込む
_, err = file.WriteString(person.Name)
if err != nil {
log.Fatal(err)
}
// レスポンスとしてステータスコード201を送信
w.WriteHeader(http.StatusCreated)
} else if r.Method == "GET" {
// パラメータを取得
id, err := strconv.Atoi(r.URL.Query().Get("id"))
if err != nil {
log.Fatal(err)
}
filename := fmt.Sprintf("%d.txt", id)
b, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatal(err)
}
// personを生成
person := Person{
ID: id,
Name: string(b),
}
// レスポンスにエンコーディングしたHTMLを書き込む
t.Execute(w, person)
}
}
サーバを起動したら、http://
にアクセスすると、

まとめ
本章では、