Friday the 13th - Gopher vs JSON!!

今日は13日の金曜日！
ということでせっかくなのでJSONネタで
![gopherVSjson.gif](https://qiita-image-store.s3.amazonaws.com/0/31437/38267395-9056-a673-b7a5-a68d27650c78.gif)
The Gopher character is based on the Go mascot designed by Renée French.

# encoding/json
GoにはJSONを扱う標準パッケージが含まれています。
[encoding/json](http://golang.org/pkg/encoding/json/)
3rd partyのJSONライブラリでも探してきて試そうかとも思ったのですがそれ以前に標準パッケージを全然把握していない自分に気付いたので、そちらを試すことにしました。

## エンコード
手っ取り早くエンコードするには[Marshal関数](http://golang.org/pkg/encoding/json/#Marshal)を使います。インデント整形したい場合は[MarshalIndent](http://golang.org/pkg/encoding/json/#MarshalIndent)を使います。

###structのエンコード
structをJSONオブジェクトにエンコードします。

```go:encode1.json
package main

import (
	"encoding/json"
	"log"
)

type Book struct {
	Title   string
	Price   int
	Authors []string
}

type Bookshelf struct {
	Books   []Book
}

func main(){
	bs := Bookshelf{
		[]Book{
			{"Gopher vs JSON!", 1000, []string{"山田　太郎", "鈴木　一郎"}},
			{"Go phrasebook", 2000, []string{"ほげ　ぼすけて"}},
			{"Go! Gopher", 3000, []string{"テスト"}},
		},
	}
	b, err := json.MarshalIndent(bs, "", "\t")
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%s", b)
}
```

####結果
```json
{
	"Books": [
		{
			"Title": "Gopher vs JSON!",
			"Price": 1000,
			"Authors": [
				"山田　太郎",
				"鈴木　一郎"
			]
		},
		{
			"Title": "Go phrasebook",
			"Price": 2000,
			"Authors": [
				"ほげ　ぼすけて"
			]
		},
		{
			"Title": "Go! Gopher",
			"Price": 3000,
			"Authors": [
				"テスト"
			]
		}
	]
}
```

Marshal関数の戻り値はUTF8でエンコードされたbyte配列です。

structの場合は公開されたフィールドのみエンコードされます。フィールド名がそのままJSONオブジェクトのキーになるので、このままだと大文字開始のキーしか出力できません。またnil値がJSONのnullにエンコードされるのですが、string等nilを代入出来ない型だとnullを出力できません。
タグ（Javaでいうところのannotationみたいなもの？）をつかうことでエンコードルールをもう少し細かく制御することが出来ます。

```go
type Book struct {
	Title   string   `json:"title"`
	Price   int      `json:"price,omitempty"`
	Authors []string `json:"authors"`
	Secret  string   `json:"-"`
}
```

`json:"title"`はJSONのキーを"title"とすることを表します。
`omitempty`オプションが指定されたフィールドは0値のときにエンコード非対象となります。JSONで0値とnilを区別したいときはどうすればいいのかな。ちょっと気持ち悪いけどinterface{}型にするしかないか。
公開したいけどJSONには出力したくないフィールドには`json:"-"`と記述します。

### mapのエンコード
mapもstructと同様にエンコードできます。下記サンプルコードはstructのエンコードサンプルをmapに書き直したものです。mapのキーの型はstringである必要があります。値の型は何が入るか分からないのでinterface{}型にしてます。

```go:encode2.go
package main

import (
	"encoding/json"
	"log"
)

func main(){
	bs := map[string]interface{}{
		"Books": []map[string]interface{} {
			map[string]interface{} {
				"Title" : "Gopher vs JSON!",
				"Price" : 1000,
				"Authors" : []string{"山田　太郎", "鈴木　一郎"},
			},
			map[string]interface{} {
				"Title" : "Go phrasebook",
				"Price" : 2000,
				"Authors" : []string{"山田　太郎", "鈴木　一郎"},
			},
			map[string]interface{} {
				"Title" : "Go! Gopher",
				"Price" : 3000,
				"Authors" : []string{"テスト"},
			},
		},
	}
	b, err := json.MarshalIndent(bs, "", "\t")
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%s", b)
}
```

### Marshaler
[Marshaler](http://golang.org/pkg/encoding/json/#Marshaler)インターフェースを実装することでtypeに対して独自のエンコード処理を実装することが出来ます。下記コードではAuthorsというtypeを定義してMarshalJSONメソッドを実装し、stringスライスをハイフン区切りのJSON文字列として出力する様にしました。※エスケープ処理は割愛しています

```go:encode4.go

type Authors []string

func (a Authors) MarshalJSON() ([]byte, error) {
	return []byte("\"" + strings.Join(a, "-") + "\""), nil
}

type Book struct {
	Title   string  `json:"title"`
	Price   int     `json:"price,omitempty"`
	Authors Authors `json:"authors"`
	Secret  string  `json:"-"`
}

type Bookshelf struct {
	Books []Book `json:"books"`
}
```

####出力

```json
{
	"books": [
		{
			"title": "Gopher vs JSON!",
			"price": 1000,
			"authors": "山田　太郎-鈴木　一郎"
		},
		{
			"title": "Go phrasebook",
			"price": 2000,
			"authors": "ほげ　ぼすけて"
		},
		{
			"title": "Go! Gopher",
			"price": 3000,
			"authors": "テスト"
		},
		{
			"title": "",
			"authors": "hoge"
		}
	]
}
```

どうでもいいことですがMarshalerのスペル、L2個じゃないのかな？

### Encoder
[Encoder](http://golang.org/pkg/encoding/json/#Encoder)を使用するとストリーム(io.Writer)に対してJSONエンコードを連続出力することができます。

```go:encode5.go
package main

import (
	"encoding/json"
	"log"
	"os"
)

type Book struct {
	Title   string   `json:"title"`
	Price   int      `json:"price,omitempty"`
	Authors []string `json:"authors"`
}

func main() {
	enc := json.NewEncoder(os.Stdout)
	
	if err := enc.Encode(&Book{"Gopher vs JSON!", 1000, []string{"山田　太郎", "鈴木　一郎"}}); err != nil {
		log.Fatal(err)
	}
	if err := enc.Encode(&Book{"Go phrasebook", 2000, []string{"ほげ　ぼすけて"}}); err != nil {
		log.Fatal(err)
	}
	if err := enc.Encode(&Book{"Test", 2000, []string{"ZZZZ"}}); err != nil {
		log.Fatal(err)
	}
}
```

####出力

```json
{"title":"Gopher vs JSON!","price":1000,"authors":["山田　太郎","鈴木　一郎"]}
{"title":"Go phrasebook","price":2000,"authors":["ほげ　ぼすけて"]}
{"title":"Test","price":2000,"authors":["ZZZZ"]}
```

## デコード

###structへのデコード
デコードには[Unmarshal関数](http://golang.org/pkg/encoding/json/#Unmarshal)を使用します。
定義済みstructのポインタを引数に渡せば型をよしなに変換して格納してくれます。

```go:decode3.go
package main

import (
	"encoding/json"
	"log"
)

type Book struct {
	Title   string   `json:"title"`
	Price   int      `json:"price,omitempty"`
	Authors []string `json:"authors"`
	Secret  string   `json:"-"`
}

type Bookshelf struct {
	Books []Book `json:"books"`
}

func main() {

	testJson := `
{
	"books": [
		{
			"title": "Gopher vs JSON!",
			"price": 1000,
			"authors": [
				"山田　太郎",
				"鈴木　一郎"
			]
		},
		{
			"title": "Go phrasebook",
			"price": 2000,
			"authors": [
				"ほげ　ぼすけて"
			]
		},
		{
			"title": "Go! Gopher",
			"price": 3000,
			"authors": [
				"テスト"
			]
		},
		{
			"title": "",
			"authors": [
				"hoge"
			]
		}
	]
}
`

	var b Bookshelf
	if err := json.Unmarshal([]byte(testJson), &b); err != nil {
		log.Fatal(err)
	}
	
	log.Printf("%s", b)
}
```

####結果
```
{[{Gopher vs JSON! %!s(int=1000) [山田　太郎 鈴木　一郎] } {Go phrasebook %!s(int=2000) [ほげ　ぼすけて] } {Go! Gopher %!s(int=3000) [テスト] } { %!s(int=0) [hoge] }]}
```

###恣意的なデータのデコード
事前に構造が明確なでないJSONをデコードする場合、またはstructを用意するのが面倒な場合はinterfece{}変数を渡してデコードできます。

```go:decode5.go
	var b interface{}
	if err := json.Unmarshal([]byte(testJson), &b); err != nil {
		log.Fatal(err)
	}
```

####結果

```
map[books:[map[title:Gopher vs JSON! price:%!s(float64=1000) authors:山田　太郎,鈴木　一郎] map[title:Go phrasebook price:%!s(float64=2000) authors:ほげ　ぼすけて] map[title:Go! Gopher price:%!s(float64=3000) authors:テスト] map[title: authors:hoge]]]
```

この場合キーがstringで値がinterface{}のmapが構築されているので型アサーションつかって値を取り出します。

```go
	bookshelf := b.(map[string]interface{})
	books := bookshelf["books"].([]interface{})
	book2 := books[2].(map[string]interface{})
	log.Printf("%s\n", book2["title"])
```

型アサーションちょっと面倒ですが、JSON全体を再帰的にパースする場合などはtype switchと併用すれば便利そうです。

###Unmarshaler
[Unmarshaler](http://golang.org/pkg/encoding/json/#Unmarshaler)インターフェースを実装することで独自デコード処理を記述することが出来ます。
以下かなり手抜きですがMarsharlerのサンプルで出力したハイフン区切りデータを復号しています。

```go:decode4.go
package main

import (
	"encoding/json"
	"log"
	"strings"
)

type Authors []string

func (a *Authors) UnmarshalJSON(b []byte) error {
	s := strings.TrimPrefix(string(b), "\"")
	s = strings.TrimSuffix(s, "\"")
	*a = strings.Split(s, "-")
	return nil
}

type Book struct {
	Title   string  `json:"title"`
	Price   int     `json:"price,omitempty"`
	Authors Authors `json:"authors"`
	Secret  string  `json:"-"`
}

type Bookshelf struct {
	Books []Book `json:"books"`
}

func main() {

	testJson := `
{
	"books": [
		{
			"title": "Gopher vs JSON!",
			"price": 1000,
			"authors": "山田　太郎-鈴木　一郎"
		},
		{
			"title": "Go phrasebook",
			"price": 2000,
			"authors": "ほげ　ぼすけて"
		},
		{
			"title": "Go! Gopher",
			"price": 3000,
			"authors": "テスト"
		}
	]
}
`
	var b Bookshelf
	if err := json.Unmarshal([]byte(testJson), &b); err != nil {
		log.Fatal(err)
	}
	
	log.Printf("%s", b)
}
```

### Decoder
[Decoder](http://golang.org/pkg/encoding/json/#Decoder)を使用するとストリーム(io.Reader)から連続してJSONを読み込みます。

```go:decode6.go
package main

import (
	"encoding/json"
	"log"
	"strings"
)

type Book struct {
	Title   string   `json:"title"`
	Price   int      `json:"price,omitempty"`
	Authors []string `json:"authors"`
}

func main() {
	testJson := `
	{
		"title": "Gopher vs JSON!",
		"price": 1000,
		"authors": [
		"山田　太郎",
			"鈴木　一郎"
		]
	}
	{
		"title": "Go phrasebook",
		"price": 2000,
		"authors": [
			"ほげ　ぼすけて"
		]
	}
	{
		"title": "Go! Gopher",
		"price": 3000,
		"authors": [
			"テスト"
		]
	}
`
	
	dec := json.NewDecoder(strings.NewReader(testJson))
	
	for {
		var b Book
		if err := dec.Decode(&b); err != nil {
			log.Fatal(err)
		}
		log.Printf("%s", b)
	}
}
```

####出力

```
2013/12/13 08:29:52 {Gopher vs JSON! %!s(int=1000) [山田　太郎 鈴木　一郎]}
2013/12/13 08:29:52 {Go phrasebook %!s(int=2000) [ほげ　ぼすけて]}
2013/12/13 08:29:52 {Go! Gopher %!s(int=3000) [テスト]}
2013/12/13 08:29:52 EOF
exit status 1
```

最後はDecodeがEOFを返すんですね。

## まとめ
以上です。
今回本当は[Martini](http://martini.codegangsta.io/)試す記事を書きたかったのだけど、その前にJSONを少しおさらいしとこうと思っていたら時間がなくなってしまいました。orz
普段業務ではJavaで[JSONIC](http://jsonic.sourceforge.jp/)使ってるのですがencoding/json使い勝手も似ていて取っ付きやすいです。タグの使いどころもイイ感じ。ちょっとモヤモヤする部分もありますが・・・

# 今回の記事で書いたコード
https://github.com/hogedigo/gophervsjson/tree/master/src/cmd

# 参考資料
[encoding/json](http://golang.org/pkg/encoding/json/)
[JSON and Go](http://blog.golang.org/json-and-go)