kakakakakku blog

Weekly Tech Blog: Keep on Learning!

The Go Blog : JSON and Go を読んだ

今さらながら The Go Blog : JSON and Go を読んで,Golang で JSON を扱う方法を学んでいた.Golang を実践的に書いている人にとっては初歩的すぎる内容だけど,個人的にメモを残しておく.

encoding/jsonjson.Unmarshal を使うと,JSON をパースすることができる.json.Unmarshal は引数に []byteinterface{} を受けるため,定義した 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.Readerioutil.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 は個人的に今もずっと使ってる!)

kakakakakku.hatenablog.com