GolangでPostgreSQLのJSONデータ型を読み書きする

## tl;dr
結論から言うと全然難しくないです。
読み込む時はjsonのキーを指定してintやstringで読み込むか、jsonを丸ごと取得して構造体にunmarshalします。
書き込むときも構造体をmarshalしてSQLに渡すだけです。

## テーブルの用意
ユーザ、データベースの作成は割愛します。

```sql
$ psql -U test_user test_db
test_db=> CREATE TABLE jojo ( id serial, chapter int, data json );

INSERT INTO jojo VALUES (1, '{ "title": "ファントムブラッド", "character": { "hero": "ジョナサン・ジョースター" } }');
INSERT INTO jojo VALUES (2, '{ "title": "戦闘潮流", "character": { "hero": "ジョセフ・ジョースター" } }');
INSERT INTO jojo VALUES (3, '{ "title": "スターダストクルセイダース", "character": { "hero": "空条承太郎" } }');
```

このとき、`hero`のフィールドを指定してSELECTするには以下のようにします。

```sql
test_db=> SELECT data->'character'->>'hero' AS hero FROM jojo;
           hero
--------------------------
 ジョナサン・ジョースター
 ジョセフ・ジョースター
 空条承太郎
(3 rows)
```

## GolangでPostgreSQLのJSONデータを扱う

### とりあえずデータを読んでみる
PostgreSQL用のドライバが必要なので取得します。

```bash
$ go get github.com/lib/pq
```

JSONのことは意識せず、直接SELECTしてみます。

```go:postgersql.go
package main

import (
	"database/sql"
	"fmt"
	_ "github.com/lib/pq" // 読み込むだけで直接は使わない
)
func main() {
	db, _ := sql.Open("postgres", "user=test_user password=password dbname=test_db sslmode=disable")
	defer db.Close()

	// SELECT（JSONのフィールドを直接指定）
	rows, _ := db.Query("SELECT data->'character'->>'hero' as hero FROM jojo;")

	//ポインタが先頭より前なので1つ目でも`Next()`が必要
	for rows.Next() {
		var hero string // 普通にstringとして読める
		rows.Scan(&hero)
		fmt.Println(hero)
	}
}
```

`->`と`->>`を使ってフィールドを直接読み込めばJSONであることは特に意識しなくても読み出すことができます。

```bash
$ go run postgresql.go

> ジョナサン・ジョースター
> ジョセフ・ジョースター
> 空条承太郎
```

### JSONのデータを読み書きする

さて、本題です。テーブルのスキーマ状態に合わせて構造体を作り、`json.Marshal()`、`json.Unmarshal()`を使ってデータを扱います。

```go:postgresql.go
package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/k0kubun/pp" //見やすさのために利用させていただいてます
	_ "github.com/lib/pq"
)

type Jojo struct {
	ID       int64
	Chapter  int
	JojoData json.RawMessage
}

type JojoData struct {
	Title     string    `json:"title"`
	Character Character `json:"character"`
}

type Character struct {
	Hero string `json:"hero"`
}

func main() {
	db, _ := sql.Open("postgres", "user=test_user password=password dbname=test_db sslmode=disable")
	defer db.Close()

	// INSERT
	insertQuery := "insert into jojo (chapter, data) values($1, $2)"

	// 構造体としてデータを作成します
	newCharacter := Character{Hero: "東方仗助"}
	newData := JojoData{Title: "ダイヤモンドは砕けない", Character: newCharacter}
	
	// JSON化して第4部のデータとして挿入します
	d, _ := json.Marshal(newData)
	db.Exec(insertQuery, 4, d)
	

	// SELECT
	rows, _ := db.Query("SELECT id, chapter, data as hero FROM jojo ORDER BY chapter ASC;")

	for rows.Next() {
		var jojo Jojo
		rows.Scan(&jojo.ID, &jojo.Chapter, &jojo.JojoData)

		// 取得したデータを構造体にマッピングします
		var d JojoData
		json.Unmarshal(jojo.JojoData, &d)

		pp.Println(fmt.Sprintf("第%d部", jojo.Chapter))
		pp.Println(d)
	}

}
```

実行してみます。

```bash
$ go run postgresql.go

"第1部"
main.JojoData{
  Title:     "ファントムブラッド",
  Character: main.Character{
    Hero: "ジョナサン・ジョースター",
  },
}
"第2部"
main.JojoData{
  Title:     "戦闘潮流",
  Character: main.Character{
    Hero: "ジョセフ・ジョースター",
  },
}
"第3部"
main.JojoData{
  Title:     "スターダストクルセイダース",
  Character: main.Character{
    Hero: "空条承太郎",
  },
}
"第4部"
main.JojoData{
  Title:     "ダイヤモンドは砕けない",
  Character: main.Character{
    Hero: "東方仗助",
  },
}
```

## まとめ

GolangでPostgreSQLのJSONデータ型を読み書きする方法について書きました。

特にJSONだからといって難しいことはなく、普通にJSONのエンコード・デコードをしてあげれば大丈夫でした。実際は[gorp](https://github.com/go-gorp/gorp)や[gorm](https://github.com/jinzhu/gorm) を使うことが多いと思いますが、上の基本を知っていればあまり迷うことはなさそうです。