LoginSignup
10
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-02-14

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

基本

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

test.json

{
 name: 'hoge',
 age: 21
}

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

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

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
}

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

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出来ません。

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すれば良いです。

ソース全文は以下.

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している。


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

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

10
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
7