Goで異なるJSONを判別してStructにUnmarshalする

WebAPIを利用したコードをgolangで書く場合など、APIのレスポンスをStructにUnmarshalすることはよくあります。

## 基本

以下の様なjsonを返すサーバーを書く。

```json:test.json

{
 name: 'hoge',
 age: 21
}

```

サーバー側の実装。
localhost:8000にアクセスするとTwitterのStreamingAPIのように延々とレスポンスを返している。

ref:http://qiita.com/kyokomi/items/cf1dd4fa85f1a99a42cc

```go:server.go

package main

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"time"
)

func main() {
	http.HandleFunc("/", Streaming)
	http.ListenAndServe(":8000", nil)
}

func Streaming(w http.ResponseWriter, req *http.Request) {
	w.WriteHeader(200)

	for {
		u := new(User)
		u.Name = "a"
		u.Age = 22
		enc, _ := json.Marshal(u)
		w.Write(enc)
		w.Write([]byte("\n"))
		w.(http.Flusher).Flush()

		time.Sleep(1 * time.Second)
	}
}

type User struct {
	Name string
	Age  int64
}

```


これを受ける側のクライアントのコードはこんな感じ。



```go:main.go

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"github.com/k0kubun/pp"
)

func main() {

	res, err := http.Get("http://localhost:8000")
	if err != nil {
		log.Fatalln(err)
	}

	scanner := bufio.NewScanner(res.Body)
	for {
		if ok := scanner.Scan(); !ok {
			break
		}
		u := new(User)
		if err = json.Unmarshal(scanner.Bytes(), &u); err != nil {
				break
		}
        pp.Print(u)
	}
}

type User struct {
	Name string
	Age  int64
}

```

単純に延々と受けて標準出力に出すだけ。


## 異なる形式のJSONがランダムに返される場合

ここからが本旨。
ランダムに別の形のJSONが返される場合はどうすればいいか。
フィールドなどが全く違うJSONが返されるので先ほどのUserという型のStructではUnmarshal出来ません。

```go:multi_json_servergo


package main

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"time"
)

func main() {
	http.HandleFunc("/", Streaming)
	http.ListenAndServe(":8000", nil)
}

func Streaming(w http.ResponseWriter, req *http.Request) {
	w.WriteHeader(200)

	for {
		rand.Seed(time.Now().Unix())
		rand := rand.Intn(2)
		if rand == 0 {
			u := new(User)
			u.Name = "a"
			u.Age = 22
			enc, _ := json.Marshal(u)
			w.Write(enc)
		} else {
			e := new(Event)
			e.Title = "title"
			enc, _ := json.Marshal(e)
			w.Write(enc)
		}
		w.Write([]byte("\n"))
		w.(http.Flusher).Flush()

		time.Sleep(1 * time.Second)
	}

	fmt.Fprintf(w, "hello end\n")
}

type User struct {
	Name string
	Age  int64
}

type Event struct {
	Title string
}


```

Userという型とEventという型のjsonが返ります。



このような場合、クライアント側はどうすればいいでしょうか。


## interfaceで受けてフィールド文字列で判別してUnmarshalする

StructにUnmarshalする前に一旦interfaceにUnmarshalすることで、どんなjsonでも受ける事ができます。

そして、その受けたinterfaceにどのフィールドがあるかを判定し、そのレスポンスに合ったStructにUnmarshalすれば良いです。

ソース全文は以下.




```go:multi_json_client.go

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"github.com/k0kubun/pp"
)

func main() {

	res, err := http.Get("http://localhost:8000")
	if err != nil {
		log.Fatalln(err)
	}

	scanner := bufio.NewScanner(res.Body)
	for {
		if ok := scanner.Scan(); !ok {
			break
		}
		var result interface{}

		if err = json.Unmarshal(scanner.Bytes(), &result); err != nil {
			fmt.Println(err)
			continue
		}
		msg := result.(map[string]interface{})
		if _, ok := msg["Name"]; ok {
			u := new(User)
			if err = json.Unmarshal(scanner.Bytes(), &u); err != nil {
				break
			}
			pp.Print(u)
		}
		if _, ok := msg["Title"]; ok  {
			e := new(Event)
			if err = json.Unmarshal(scanner.Bytes(), &e); err != nil {
				break
			}
			pp.Print(e)
		}

	}
}

type User struct {
	Name string
	Age  int64
}

type Event struct {
	Title string
}



```



要は、jsonをinterfaceで取得して"Name"というフィールドがあればUser型のStruct,"Title"というフィールドがあればEventというStructへUnmarshalしている。

```go

var result interface{}

  if err = json.Unmarshal(scanner.Bytes(), &result); err != nil {
            fmt.Println(err)
            continue
        }

        msg := result.(map[string]interface{})
        if _, ok := msg["Name"]; ok {
            u := new(User)
            if err = json.Unmarshal(scanner.Bytes(), &u); err != nil {
                break
            }
            pp.Print(u)
        }
        if _, ok := msg["Title"]; ok  {
            e := new(Event)
            if err = json.Unmarshal(scanner.Bytes(), &e); err != nil {
                break
            }
            pp.Print(e)
        }

```

動作はこんな感じになる。

![json.gif](https://qiita-image-store.s3.amazonaws.com/0/2341/09596305-489e-35d8-2a84-7b47f2c8630f.gif)



TwitterのUserStreamは異なるJSONが延々吐かれるのでこんな感じで判別するのが良さそう。
2度Unmarshalするので非効率さはありますので、もっと良い方法を知っていれば教えて下さい。