golangが好きだ.
golangでjsonを扱う際,「こういうことできないのかなー」といつも迷って,やり方を頑張って探すことが多いので,少しまとめておこうと思う.
json文字列に変数を埋めたい
golangでjsonを扱うサンプル等で,よく以下のような記述を見かける.
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) }
こんな感じ.