goでjsonを扱うときにいつも迷うこと

golangが好きだ.

golangjsonを扱う際,「こういうことできないのかなー」といつも迷って,やり方を頑張って探すことが多いので,少しまとめておこうと思う.

json文字列に変数を埋めたい

golangjsonを扱うサンプル等で,よく以下のような記述を見かける.

jsonBlob:= []byte(`[
    {"name": "KanaAsumi", "age": 34},
    {"name": "RyokoShintani", "age": 36}
]`)

type Seiyu struct {
    Name string
    Age int
}

var seiyus []Seiyu
err := json.Unmarshal(jsonBlob, &seiyus)

このように文字列でjsonの構造を定義する際,中に変数を埋め込みたいときがある.

で,これってjsonの構造を記述していたとしても,所詮はただのstringなので,ただのstringの変数埋め込みで表現できる.

name := "AyaneSakura"
age := 23

jsonBlob := []byte(fmt.Sprintf(`[
    {"name": "KanaAsumi", "age": 34},
    {"name": "RyokoShintani", "age": 36},
    {"name": "%s", "age": %d}
]`, name, age))

こんな感じ.Sprintfすごく便利.

structにマッピングする際に型変換する

例えば以下のようなjsonを扱うとする.

{
  "name": "KanaAsumi",
  "age": "34"
}

これを以下のようなstructにマッピングしたい.

type Seiyu struct {
    Name string
    Age int
}

ただしこれをそのままUnmarshalしようとするとエラーとなる.

json: cannot unmarshal string into Go struct field Seiyu.Age of type int

なぜなら,jsonで定義されているageはintではなくstring形式となっている.

そのため,これをそのままマッピングするには以下のようなstructが必要になってしまう.

type Seiyu struct {
    Name string
    Age string
}

これなら,そのままUnmarshalすることができる. しかし,Ageはintではなくstringになっている.

これを,ちゃんとAgeがintとなるようなUnmarshalをしたい

この程度の型変換のために,一度何かしらの構造体にマッピングしたり,interfaceを使ったりするのはだるい.

で,どうやるかというと,以下のようにしてやる.

type Seiyu struct {
    Name string `json:"name"`
    Age int     `json:"age,string"`
}

こうすることで,ageはstringとして読み込みintに型変換されたものを得ることができる.

type Seiyu struct {
    Name string `json:"name"`
    Age  int    `json:"age,string"`
}

func main() {
    jsonBlob := []byte(`
{
  "name": "KanaAsumi",
  "age": "34"
}
`)

    var seiyu Seiyu
    err := json.Unmarshal(jsonBlob, &seiyu)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("seiyu: %+v", seiyu)
}

structにマッピングする際にtimeの型変換をする

jsonに時刻文字列が混ざっていることがよくある.

{
  "event": "Click",
  "timestamp": "2017-12-30T11:25:30+09:00"
}

上記の例は時刻の形式をRFC3339に合わせてあるので,このままtime型にマッピングすることができる.

type Message struct {
    Event     string
    Timestamp time.Time
}

func main() {
    jsonBlob := []byte(`
{
  "event": "Click",
  "timestamp": "2017-12-30T11:25:30+09:00"
}
`)

    var message Message
    err := json.Unmarshal(jsonBlob, &message)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("message: %+v", message)

}

例えばこれをjsonとしてmarshalする際,JSTではなく常にUTCとして書き出したいようなことがある. こういう場合は変換メソッドを持つ新しいstructを自分で用意し,自分で変換してやる必要がある.

type utcTime struct {
    time.Time
}

func (u utcTime) format() string {
    return u.Time.UTC().String()
}

func (u utcTime) MarshalJSON() ([]byte, error) {
    return []byte(`"` + u.format() + `"`), nil
}

type Message struct {
    Event     string  `json:"event"`
    Timestamp utcTime `json:"timestamp"`
}

func main() {
    loc, _ := time.LoadLocation("Asia/Tokyo")
    m := Message{
        "Click",
        utcTime{time.Date(2014, 8, 25, 0, 0, 0, 0, loc)},
    }

    bytes, _ := json.Marshal(m)
    log.Printf("%s", bytes)
}

こんな感じ.

参考

qiita.com