GoでJSONをパースするときに考えることになるアレこれ

GoでJSONをパースするときに、あれこれ考えるのだけど
きちんとやっていくと、どうしてもうまく実装できなくなって困っているという話。
先に書いておきますが、結論はないです。

# JSONをパースするときの問題

そもそも、JSONを簡単に取り扱う必要がなぜあるのか、について。
標準では、JSONをパースする場合 `json.Unmarshal`を使ったり`json.NewDecoder`を使ったりする。

例えば、User構造体に

```go
type User struct {
  Name string
  Age  uint16
}

func main() {
  var jsonStr = `{
    "Name": "Ken", "Age": 24
  }`

  var u User
  json.Unmarshal([]byte(jsonStr), &u)
  fmt.Println(u) // {Ken 24}
}
```

上の場合、JSONの値にプロパティがないと何も値が入らない。
ところが、Goでは初期値として型の0値が入る。
なので、上のUser構造体の場合、`Age`が設定されないままのため
値が0として取り扱われしまう。

これだと、本当に0歳なのか設定ミスなのかわからなくなってしまう。

```go
func main() {
  var jsonStr = `{
    "Name": "Bob"
  }`

  var u User
  json.Unmarshal([]byte(jsonStr), &u)
  fmt.Println(u) // {Ken 0}
}
```

これを解決するために、元となる構造体のフィールドをポインタ型にするとnilチェックができるようになる。

```go
type User struct {
  Name *string
  Age  *uint16
}

func main() {
  var jsonStr = `{
    "Name": "Ken"
  }`

  var u User
  json.Unmarshal([]byte(jsonStr), &u)
  fmt.Println(u) // {0x2081ec2a0 <nil>}
}
```

ただこの場合、このJSONの結果がポインタ型だと意識して使わないといけなくなるので
使い勝手が悪くなる。なので、JSONを取り扱いやすいライブラリが欲しくなる。

# よくあるJSONパッケージ

JSONを簡単に扱いたい場合に、以下のようなパッケージがある

* [jason](https://github.com/antonholmquist/jason)
* [go-simplejson](https://github.com/bitly/go-simplejson)
* [go-scan](https://github.com/mattn/go-scan)

## jason

https://github.com/antonholmquist/jason

```go
import "github.com/antonholmquist/jason"

func main() {
  var jsonStr = `{
    "Name": "Ken", "Age": 32
  }`

  var u User
  var err error

  v, err := jason.NewObjectFromBytes([]byte(jsonStr))
  if err != nil {
    panic(err)
  }

  u.Name, err = v.GetString("Name")
  if err != nil {
    panic(err)
  }

  var age float64
  age, err = v.GetNumber("Age")
  if err != nil {
    panic(err) // ここで設定を検知する
  }
  u.Age = uint16(age)

  fmt.Println(u) // {Ken, 32}
}
```

## go-simplejson

https://github.com/bitly/go-simplejson

```go
import (
  simpleJson "github.com/bitly/go-simplejson"
)

func main() {
  var jsonStr = `{"Name": "Ken", "Age": 32}`

  js, err := simpleJson.NewJson([]byte(jsonStr))
  if err != nil {
    panic(err)
  }

  var u User

  u.Name, err = js.Get("Name").String()
  if err != nil {
    panic(err)
  }

  var age uint64
  age, err = js.Get("Age").Uint64()
  if err != nil {
    panic(err) // ここで設定を検知する
  }
  u.Age = uint16(age)

  fmt.Println(u) // {Ken, 32}
}
```

## go-scan

https://github.com/mattn/go-scan

```go
import (
  scan "github.com/mattn/go-scan"
)

func main() {
  var jsonStr = `{"Name": "Ken", "Age": 32}`
  var u User

  r := strings.NewReader(jsonStr)
  if err := scan.ScanJSON(r, "/Name", &u.Name); err != nil {
    panic(err)
  }

  r = strings.NewReader(jsonStr)
  if err := scan.ScanJSON(r, "/Age", &u.Age); err != nil {
    panic(err)
  }

  fmt.Println(u) // {Ken, 32}
}
```

ちなみに、`ScanTree()`のほうが使いやすいと思う。

```go
func main() {
  var jsonStr = `{"Name": "Ken", "Age": 32}`
  var u User

  var i interface{}
  if err := json.Unmarshal([]byte(jsonStr), &i); err != nil {
    panic(err)
  }

  if err := scan.ScanTree(i, "/Name", &u.Name); err != nil {
    panic(err)
  }

  if err := scan.ScanTree(i, "/Age", &u.Age); err != nil {
    panic(err)
  }

  fmt.Println(u) // {Ken, 32}
}
```

### ベンチマーク

上3つのライブラリのベンチマークの結果はこんな感じ。

```zsh
% go test -bench .
testing: warning: no tests to run
PASS
BenchmarkJason	  100000	     20752 ns/op
BenchmarkSimpleJSON	  200000	      9710 ns/op
BenchmarkGoScan	  100000	     18262 ns/op
```

[テストコード](https://gist.github.com/hiroosak/5a66dc9ffe1148641674)

# 型にUnmarshalを実装する

型に[json.Unmarshaler](http://golang.org/src/encoding/json/decode.go?s=2910:2969#L76)インタフェースを実装することで、
JSONをデコードしたときに、そちらを呼び出すことができる。

```go
package main

import (
  "encoding/json"
  "fmt"

  scan "github.com/mattn/go-scan"
)

type User struct {
  Name string
  Age  uint16
}

func (u *User) UnmarshalJSON(data []byte) error {
  var i interface{}

  if err := json.Unmarshal(data, &i); err != nil {
    return err
  }
  if err := scan.ScanTree(i, "/Name", &u.Name); err != nil {
    return err
  }
  if err := scan.ScanTree(i, "/Age", &u.Age); err != nil {
    return err
  }
  return nil
}

func main() {
  var jsonStr = `{"Name": "Ken", "Age": 32}`
  var u User
  err := json.Unmarshal([]byte(jsonStr), &u)
  if err != nil {
    panic(err)
  }
  fmt.Printf("Name: %s Age: %d\n", u.Name, u.Age) // name: Ken Age: 32
}
```

https://gist.github.com/hiroosak/af65464863cda7dc494a

# ネストされたJSONの場合

ここから上手いやり方がわからない話。
たとえば、下のようなネストされたデータを持ちたい場合。
そして `Article.Title` と `Article.Content` を必須にしたい場合。

```json
{
    "Name": "Ken",
    "Age": 32,
    "Articles": [
      {"Title": "Title A", "Content": "content"},
      {"Title": "Title B", "Content": "content"}
    ]
}
```

構造体も`Article`を入れる。

```go
type User struct {
  Name     string
  Age      uint16
  Articles []Article
}

type Article struct {
  Title   string
  Content string
}
```

前の段落で書いたUnmarshalerインターフェースを実装しなければ、
構造体への展開はよろしくやってくれる。

```go
func main() {
  var jsonStr = `{
    "Name": "Ken",
    "Age": 32,
    "Articles": [
      {"Title": "Title A", "Content": "content"},
      {"Title": "Title B", "Content": "content"}
    ]
  }`
  var u User

  err := json.Unmarshal([]byte(jsonStr), &u)
  if err != nil {
    panic(err)
  }

  fmt.Println(u) // {Ken 32 [{Title A content} {Title B content}]}
}
```

ところが、上のUnmarshalerインターフェースを実装すると、これができなくなる。
なので、`func (u *User) UnmarshalJSON(data []byte) error`側に直接記載する必要が出てくる。

```go
func (u *User) UnmarshalJSON(data []byte) error {
  js, err := simpleJson.NewJson(data)
  if err != nil {
    return err
  }

  u.Name, err = js.Get("Name").String()
  if err != nil {
    return err
  }

  var age uint64
  age, err = js.Get("Age").Uint64()
  if err != nil {
    return err
  }
  u.Age = uint16(age)

  // Article
  var bytes []byte
  bytes, err = js.Get("Articles").Encode()
  if err != nil {
    return err
  }

  var as []Article
  if err := json.Unmarshal(bytes, &as); err != nil {
    return err
  }
  u.Articles = as

  return nil
}
```

`Article`も`User`のように`UnmarshalJSON`を実装する

```go
func (a *Article) UnmarshalJSON(data []byte) error {
  js, err := simpleJson.NewJson(data)
  if err != nil {
    return err
  }

  a.Title, err = js.Get("Title").String()
  if err != nil {
    return err
  }

  a.Content, err = js.Get("Content").String()
  if err != nil {
    return err
  }
  return nil
}
```

https://gist.github.com/hiroosak/2cc2ab4a8da6cc7d78c4

# バリデーションとパースを分ける

そうなるともうバリデーションとパースを完全に分けたほうがいいんじゃなかろうかってなる。
なので、json-schemaを用意して、[gojsonschema](https://github.com/xeipuuv/gojsonschema)を使って最初にバリデーションをかけてみる

```go
func main() {
	var jsonStr = `{
		"Name": "Ken",
		"Age": 32,
		"Articles": [
			{"Title": "Title A", "Content": "content A"},
			{"Title": "Title B", "Content": "content B"}
		]
	}`
 
	schema := `{
		"type": "object",
		"properties": {
			"Name": {
				"type": "string"
			},
			"Age": {
				"type": "integer"
			},
			"Articles": {
				"type": "array",
				"items": {
					"type": "object",
					"properties": {
						"Title": {
							"type": "string"
						},
						"Content": {
							"type": "string"
						}
					},
					"additionalProperties": false,
					"required": ["Title", "Content"]
				}
			}
		},
		"additionalProperties": false,
		"required": ["Name", "Age", "Articles"]
	}`
 
	var j map[string]interface{}
	err := json.Unmarshal([]byte(schema), &j)
	if err != nil {
		panic(err)
	}
 
	schemaDocument, err := gojsonschema.NewJsonSchemaDocument(j)
	if err != nil {
		panic(err)
	}
 
	var jsonBody interface{}
	err = json.Unmarshal([]byte(jsonStr), &jsonBody)
	if err != nil {
		panic(err)
	}
 
	result := schemaDocument.Validate(jsonBody)
	if result.Valid() == false {
		panic("err")
	}
 
	var u User
	err = json.Unmarshal([]byte(jsonStr), &u)
	if err != nil {
		panic(err)
	}
 
	fmt.Println(u) // {Ken 32 [{Title A content} {Title B content}]}
}
```

https://gist.github.com/hiroosak/c70066ed92b0c847f499

これで、`Articles`の中身もjson-schemaで指定した通りにチェックしてくれる。

# まとめ

いろいろとグダグダ書いてみたけれど
まとめるとこんな感じ。

* [json.Unmarshal](http://golang.org/pkg/encoding/json/#Unmarshal)で簡単にJSONをパースできる
* ゼロ値が意図的に設定されたものなのかを確認したい場合
    * ポインタ型で受け取って`nil`チェックをする
        * ポインタ把握が若干ツラい
    * `map[string]interface{}`で受け取ってチェックをする *(上では記載してない)*
        * 型キャストがたくさんできてツラい
    * ライブラリを使って値をチェックする
        * [jason](https://github.com/antonholmquist/jason)
        * [go-simplejson](https://github.com/bitly/go-simplejson)
        * [go-scan](https://github.com/mattn/go-scan)
    * 処理を書く場所は？
        * 毎回Unmarshalする前に書く
        * 値を入れたい構造体[Unmarshaler](http://golang.org/pkg/encoding/json/#Unmarshaler)インタフェースを実装する
* ネストされたJSONをパースしたい場合
    * 構造体をネスト構造にして[json.Unmarshal](http://golang.org/pkg/encoding/json/#Unmarshal)
    * ゼロ値のチェックが必要な場合は？
        * Unmarshalerインターフェースを実装して、そこに手動で書く
        * 組み合わせがあったときにつらい
    * バリデーションとパースを分けて考える
        * バリデーション
            * `jason` `go-simplejson` `go-scan`とか
            * json-schemaを用意して[gojsonschema](https://github.com/xeipuuv/gojsonschema)を使って、パースをする


状況に応じてバリエーションがあってなかなか
コレだという感じがない。
なんかもっと良い方法があるのではないかとモヤモヤ思う今日この頃です。