Go言語とJSON


```json
{
  "Id": 1
}
```

誰がこんなもん欲しがるって言うんだよォー！！
クライアント側(JavaScriptとかだ)が明らかに困るだろ！

## 現状の確認

### 素直なコードとJSON

先のJSONは[以下のようなコード](https://play.golang.org/p/ijTMKHF5z_)で生成した。

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Sample struct {
	Id int
}

func main() {
	data := Sample{1}
	bytes, err := json.Marshal(data)
	if err != nil {
		return
	}
	fmt.Println(string(bytes))
}
```

Go言語がわからない人でもきっとなんとなく読めるだろう。
素直だ。わかりやすい。このコードが`"Id"`ではなく`"id"`というkeyでJSON生成してくれれば何も問題はなかったのに。

### 少し書くのがめんどいコードとJSON

もちろん、出力されるkeyの名前を変えるくらい、わけない。
[こう](https://play.golang.org/p/t_OScxBxPe)すればいいんだ。

```go
type Sample struct {
	Id int `json:"id"`
}
```

実に簡単。[タグ](http://golang.org/ref/spec#Struct_types)というメタデータを書く仕組みに則って書くだけだ。
もちろん、structのプロパティ全てに対して書く必要がある。
`json.Marshal(data, &json.Option{LowerCamel:true}`みたいな書き方があるはずだ…！って？
僕の調べた限り、ない。
頑張って全てに書くんだ。
フィールド名変えたらタグの中身も変えるんだ。
いちいちやるんだ。全部やるんだ。

### 標準ライブラリ以外のJSONライブラリは？

僕がざっくり調べた限り、広く使われているものはないみたいだ。
色々なフレームワークが標準ライブラリの`encoding/json`で動作することを期待している。
逃れることはできない…！

### json.Marshaler

`encoding/json`では、[Marshaler](http://golang.org/pkg/encoding/json/#Marshaler)というインタフェースを用意していて、そのメソッドを実装すれば挙動を自分で上書きできる。[こんな感じ](https://play.golang.org/p/V0u_EG7wXA)(ちな[Unmarshaler](http://golang.org/pkg/encoding/json/#Unmarshaler)もある)

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Sample struct {
	Id int
}

func (s *Sample) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf("{\"id\":%d}", s.Id)), nil
}

func main() {
	data := &Sample{1}
	bytes, err := json.Marshal(data)
	if err != nil {
		return
	}
	fmt.Println(string(bytes))
}
```

なので、自分でMarshalerを書くという逃げ道もあるだろう。
なお、jsonのstream encoder/decoder的なものは標準ライブラリには存在していない。
`encoding/json`内部には持っているのだが、非公開なのだ。
[JSR353](https://jcp.org/en/jsr/detail?id=353)みたいなの欲しい。

## 現実的な対策

### 泣きながら頑張ってタグ書く

たぶんみんなこうやってるんだろうな

### json.Marshalerで頑張る

汎用的な変換ロジックを1つこさえて、JSONに変換する可能性があるたび、メソッド(的なの)を定義するのだ。
でもタグ情報が足りなくて上手く動かないコードありそう。

### go generateを使う

いい感じのコードを生成するツールを作ればいんじゃね？
AST触りやすいGoさん偉いと思います go fmt もうまあじ

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Sample struct {
	Id int
}

func main() {
	data := &Sample{1}
	bytes, err := json.Marshal(NewSampleJson(data))
	if err != nil {
		return
	}
	fmt.Println(string(bytes))
}

// ここから自動生成

func NewSampleJson(orig *Sample) *SampleJson {
	ret := &SampleJson{}
	ret.Id = orig.Id
	return ret
}

type SampleJson struct {
	Id int `json:"id"`
}

func (s *SampleJson) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf("{\"id\":%d}", s.Id)), nil
}
```

フィールドのコピーを行うのが地味にダサい気がするんだけどどうなんだろう…。
あとは、ユーザが管理者か一般ユーザか非ログインかで出力するフィールドの制御とかも欲しいよね…。
以下みたいな感じで…。

```
type Person struct {
	Id string
	Name string
	Email string
	Himitsu string
}

func main() {
    p := &Person{"vv", "vvakame", "vvakame@example.com", "猫が好き"}
	b := NewSampleJsonBuilder()
	b.AddAll()
	b.Remove(b.Himitsu)
	bytes, err := b.Marshal(p)
}
```

だいたい[JsonPullParser](https://github.com/vvakame/JsonPullParser)だな！

[実装例](https://play.golang.org/p/VZEcl90Xby)
この例だとomitemptyで`""`とかも要素消えちゃうのでnilにしたい気がするけど`*string`はなんか日常生活がダルそうだからなぁ…みたいな気持ち。そんなことない？

## お願い

まだGo言語歴20時間くらいなので、なんかナイスな方法とか意見とかあったらコメントください。
お願いします！なんでもはしません！

--- 

追記 2015/06/25
[jwg](https://github.com/favclip/jwg)というライブラリを作って公開しました。