今さらながら The Go Blog : JSON and Go を読んで,Golang で JSON を扱う方法を学んでいた.Golang を実践的に書いている人にとっては初歩的すぎる内容だけど,個人的にメモを残しておく.
encoding/json
の json.Unmarshal
を使うと,JSON をパースすることができる.json.Unmarshal
は引数に []byte
と interface{}
を受けるため,定義した Struct にマッピングすることができる.もし JSON のキー名と Struct のフィールド名が異なる場合も問題なくて,Struct にタグを書けば,問題なくマッピングできる.
json.Unmarshal
The Go Blog に書いてある例を参考にすると,以下のように実装できる.ここまでは特に違和感もなくシンプルだった.
package main import ( "encoding/json" "fmt" ) type Message struct { Name string Body string Time int64 } func main() { b := []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`) var m Message if err := json.Unmarshal(b, &m); err != nil { return } // Name: Alice, Body: Hello, Time: 1294706395881547000 fmt.Printf("Name: %s, Body: %s, Time: %d", m.Name, m.Body, m.Time) }
任意の JSON をパースする
The Go Blog を読んでいて,interface{}
を使えば,データ構造がわからない場合など,任意の JSON をパースできることを知った.実際に試してみたところ,JSON をそのまま map[string]interface{}
に json.Unmarshal
できて,さらに Type Switch で型ごとに処理を書くことができた.実際に仕事で構造が可変な JSON をパースする必要があり,Twitter でも map が使えるとヒントを頂いたりもして,最終的にはこの interface{}
を使うことで解決できた.
package main import ( "encoding/json" "fmt" ) func main() { b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) var f interface{} if err := json.Unmarshal(b, &f); err != nil { return } m := f.(map[string]interface{}) // map[Name:Wednesday Age:6 Parents:[Gomez Morticia]] fmt.Println(m) for k, v := range m { switch vv := v.(type) { case string: // Name is string Wednesday fmt.Println(k, "is string", vv) case int: // Age is of a type I don't know how to handle fmt.Println(k, "is int", vv) case []interface{}: // Parents is an array: fmt.Println(k, "is an array:") for i, u := range vv { // 0 Gomez // 1 Morticia fmt.Println(i, u) } default: fmt.Println(k, "is of a type I don't know how to handle") } } }
タグ
Struct の部分を調べていたら,多くの記事でタグ付きの Struct が紹介されていたが,The Go Blog を読みながら実際に試してみて,理解が深まった.基本的に JSON のキー名と Struct のフィールド名が一致しているのであれば,あえてタグを書く必要はないし,大文字と小文字の区別もしないため,JSON のキー名が小文字で,Struct のフィールド名の1文字目が大文字でも問題ない.ちなみに Struct のフィールド名は1文字目を大文字 (exported) にしないとダメと書いてある.
以下にサンプルコードを載せた.JSON のキー名が message_title
の場合,大文字も小文字も区別しないため,Struct のフィールド名は Message_title
でも Message_Title
でも良い.ただし MessageTitle
にする場合は JSON のキー名を異なってしまうため, json:"message_title"
とタグを書くことによって,マッピングすることができる.タグの記法は他にもあって,nil などを除外する omitempty
や,フィールドを無視する -
もある.
package main import ( "encoding/json" "fmt" ) type Message1 struct { Message_title string } type Message2 struct { Message_Title string } type Message3 struct { MessageTitle string `json:"message_title"` } func main() { b := []byte(`{ "message_title": "Hello!" }`) var m1 Message1 if err1 := json.Unmarshal(b, &m1); err1 != nil { return } // Hello! fmt.Println(m1.Message_title) var m2 Message2 if err2 := json.Unmarshal(b, &m2); err2 != nil { return } // Hello! fmt.Println(m2.Message_Title) var m3 Message3 if err3 := json.Unmarshal(b, &m3); err3 != nil { return } // Hello! fmt.Println(m3.MessageTitle) }
json.NewDecoder
The Go Blog の最後には json.NewDecoder
も紹介されていた.特に API からレスポンスを受ける場合などは io.Reader
を ioutil.ReadAll
で []byte
に変換するよりも,直接 json.NewDecoder
を使った方が良さそう.ここは実際にベンチマークを取って確認する.
package main import ( "encoding/json" "fmt" "os" ) func main() { dec := json.NewDecoder(os.Stdin) enc := json.NewEncoder(os.Stdout) for { var v map[string]interface{} if err := dec.Decode(&v); err != nil { return } for k := range v { if k != "Name" { delete(v, k) } } if err := enc.Encode(&v); err != nil { fmt.Println(err) } } }
まとめ
1年半以上も前に Togoo を実装して以来,全然 Golang を書いてなかったけど,最近仕事で書く機会ができたので,改めて基礎部分から勉強し直している.The Go Blog も読むだけだと理解が深まらないなと思って,積極的に写経して試した.エディタは Gogland 1.0 EAP を試してみてるけど,非常に便利!とりあえず goimports を ⌥⌘I
に Keymap して使っている.Golang はまだまだビギナーだから積極的に勉強していくぞ!社内に質問できる人も多くて非常に助かってる.
(ちなみに Togoo は個人的に今もずっと使ってる!)