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: "gopher@example.org",
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": "gopher@go.org",
"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: "gopher@example.org",
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
gopherGET
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://にアクセスすると、
まとめ
本章では、