Webアプリケーションを実装する上で必ず使うであろう json パッケージについて、復習のためGoブログ JSON and Go を読みました。
せっかく読んだので和訳を記事にしようと。
目次
Encoding
JSONデータをエンコードするには、Marshal
関数を使う。
func Marshal(v interface{}) ([]byte, error)
次のような Message
構造体とそのインスタンス m
があるとする。
type Message struct {
Name string
Body string
Time int64
}
m := Message{"Alice", "Hello", 1294706395881547000}
json.Marshal
を使えばJSONエンコードされた m
を得ることができます。
b, err := json.Marshal(m)
エラーなくうまくいけば err
は nil
、b
はJSONデータを含む []byte
型になります。
全体のコードは↓な感じ。
package main
import (
"fmt"
"encoding/json"
)
type Message struct {
Name string
Body string
Time int64
}
func main() {
m := Message{"Alice", "Hello", 1294706395881547000}
b, err := json.Marshal(m)
if err == nil {
fmt.Printf("%s\n", b) // {"Name":"Alice","Body":"Hello","Time":1294706395881547000}
}
}
有効なJSONとして表現できるデータ構造のみがエンコードされます。
- JSONオブジェクトはキーには文字列のみサポート。Go の map をエンコードするには、
map[string]T
である必要がある。(T
は json パッケージがサポートしているGoの任意の型) - Channel, complex, function 型はエンコードできない。
- 循環データ構造はサポートしていない。無限ループを引き起こす。
-
ポインタはそれらが指す値にエンコードされる。ポインタが
nil
ならnull
になる。
json パッケージはパブリックのフィールドのみアクセスできる。(public, 大文字で始まるフィールド)
そのためパブリックのフィールドのみがJSONエンコードされる(JSONデータに含まれる)
Decoding
JSONデータをデコードするときは Unmarshal
関数を使う。
func Unmarshal(data []byte, v interface{}) error
まず最初にすることは、デコードしたデータを保存する変数を用意すること。
var m Message
そして、引数に JSONデータ( []byte
)と m
のポインタ(デコード結果を保存する変数のポインタ)をわたして json.Unmarshal
を呼び出す。
err := json.Unmarshal(b, &m)
b
が m
に適合する有効なJSONであれば、err
は nil
で次のように構造体 m
に b
をデコードしたデータが格納される。
m = Message{
Name: "Alice",
Body: "Hello",
Time: 1294706395881547000,
}
Unmarshal
のサンプルコードは↓な感じ。
package main
import (
"fmt"
"encoding/json"
)
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 {
fmt.Println("Failed Unmarshal err: ", err)
return
}
fmt.Printf("m = %+v\n", m) // m = {Name:Alice Body:Hello Time:1294706395881547000}
}
Unmarshal
は構造体のフィールド定義にある json タグを探索してデコードしたデータを格納するフィールドを特定している。
探索の優先順位は
JSONのキーが "Foo" の場合
- json タグに "Foo" が指定されているパブリックのフィールド
- パブリックのフィールドでフィールド名が "Foo" のもの
- パブリックのフィールドでフィールド名が "FoO" や "FOO" のように大文字・小文字を区別せず "Foo" に一致するもの
JSONデータの構造がGoの型と完全に一致しない場合はどうなるのか?
b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)
Unmarshal
は一致するフィールのみをデコードする。このケースでは、Name
フィールドのみデコードされ Food
フィールドは無視される。この挙動は巨大なJSONデータから少しの特定のキーのみピックアップして取得したいときに役立つ。また、プライベートなフィールドは Unmarshal
の影響を受けないことも意味している。
しかし、JSONデータの構造が事前にわからない場合はどうするのか?
Generic JSON with interface{}
interface {}
(空のインターフェース)型は、メソッドがゼロのインターフェイス。そのため、Goのすべての型は空のインターフェースを満たす。
空のインターフェースは、どんな値でも入るコンテナ型(入れ物)として機能する。
var i interface{}
i = "a string"
i = 2011
i = 2.777
型アサーションは、基になる具象型にアクセスする。
r := i.(float64)
fmt.Println("the circle's area", math.Pi*r*r)
基になる型が不明な場合は switch
を使う。
switch v := i.(type) {
case int:
fmt.Println("twice i is", v*2)
case float64:
fmt.Println("the reciprocal of i is", 1/v)
case string:
h := len(v) / 2
fmt.Println("i swapped by halves is", v[h:]+v[:h])
default:
// i isn't one of the types above
}
json パッケージは任意のJSONオブジェクトや配列を格納するのに map[string]interface{}
と []interface{}
を使う。有効なJSONをプレーンな interface{}
の値に unmarshal する。
bool
は JSON の boolean にfloat64
は JSON の numbers にstring
は JSON の strings にnil
は JSON の null に
Decoding arbitrary data
変数b に格納されたJSONデータがあるとする。
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
データ構造がわからない場合、Unmarshal
でデコードした結果を interface{}
に格納する。
var f interface{}
err := json.Unmarshal(b, &f)
この時点では、f
はキーが文字列でバリューが空のインターフェースとして格納された値のmapになっている。
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
データにアクセスするために f
を map[string]interface{}
に型アサーションする。
m := f.(map[string]interface{})
そして、m
を反復処理して型変換してバリューにアクセスする。
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Println(k, "is string", vv)
case float64:
fmt.Println(k, "is float64", vv)
case []interface{}:
fmt.Println(k, "is an array:")
for i, u := range vv {
fmt.Println(i, u)
}
default:
fmt.Println(k, "is of a type I don't know how to handle")
}
}
このようにして、型安全の恩恵を受けながら、未知の構造のJSONを処理する。
コードの全体像は↓な感じ。
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
var f interface{}
json.Unmarshal(b, &f)
if err := json.Unmarshal(b, &f); err != nil {
log.Fatalln("failed unmarshal. err: ", err)
}
fmt.Printf("f = %#v\n", f) // f = map[string]interface {}{"Age":6, "Name":"Wednesday", "Parents":[]interface {}{"Gomez", "Morticia"}}
m := f.(map[string]interface{})
fmt.Printf("m = %#v\n", m) // m = map[string]interface {}{"Age":6, "Name":"Wednesday", "Parents":[]interface {}{"Gomez", "Morticia"}}
for k, v := range m {
switch vv := v.(type) {
case string:
fmt.Printf("%s is string. %s\n", k, vv) // Name is string. Wednesday
case float64:
fmt.Printf("%s is float64. %f\n", k, vv) // Age is float64. 6.000000
case interface{}:
fmt.Printf("%s is interface{}. %#v\n", k, vv) // Parents is interface{}. []interface {}{"Gomez", "Morticia"}
}
}
}
Reference Types
先程の例のJSONデータを含むGoの型を定義されているとします。
type FamilyMember struct {
Name string
Age int
Parents []string
}
var m FamilyMember
err := json.Unmarshal(b, &m)
期待通りデータを FamilyMember
にアンマーシャリングしますが、よく見ると驚くべきことが起こっている。
var ステートメントで FamilyMember
構造体を割り当て、Unmarshal
関数に変数のポインタを渡したとき、Parents
フィールドは nilスライスである。Parents
フィールドに値を入れるため、裏で Unmarshal
関数が新しいスライスを割り当てている。これは Unmarshal
が参照型(ポインタ、スライス、マップ)でどう機能するかを示す典型。
次の構造体にアンマーシャリングする例を考えてみる。
type Foo struct {
Bar *Bar
}
JSONオブジェクトに Bar
キーがあれば、Unmarshal
関数は新たに Bar
を割り当てる。もしなかったら、Bar
はnilポインタのまま。
この特徴を活かした有用なパターンがある。異なるタイプのメッセージを受信するアプリケーションがあるときは、次のような「receiver」構造体を定義すると方法がある。
type IncomingMessage struct {
Cmd *Command
Msg *Message
}
そして、送信側は通信するメッセージのタイプに応じて、トップレベルのJSONオブジェクトの Cmdフィールドや Msgフィールドにデータを入力する。 Unmarshal
は、JSONを IncomingMessage
構造体にデコードするときに、JSONデータに存在するデータ構造のみを割り当てるため、処理するメッセージを知るには Cmd または Msg のいずれかが nil でないことをテストするだけで良い。
Streaming Encoders and Decoders
jsonパッケージにはJSONデータのストリームの読み書き操作をサポートしている Decoder
と Encoder
型がある。NewDecoder
とNewEncoder
関数は io.Reader
と io.Writer
インターフェースをラップしている。
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
これは、標準入力からの一連のJSONオブジェクトを読み込み、各オブジェクトの Name
フィールドを削除して、標準出力にオブジェクトを書き込むというサンプルプログラム。
package main
import (
"encoding/json"
"log"
"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 {
log.Println(err)
return
}
for k := range v {
if k != "Name" {
delete(v, k)
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
io.Reader
と io.Writer
は広く使われているため、 Decoder
と Encoder
型は、HTTP接続、WebSocket、ファイルの読み取りと書き込みなど、幅広いシナリオで使用できる。
【追加】 APIのレスポンスをデコードするサンプルコード
net/http パッケージを使って、GETリクエストを投げ、レスポンスをJSONデコードする例。
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Data struct {
Hoge string
Fuga int
}
func getData() (Data, error) {
url := "http://example.com/api"
res, err := http.Get(url)
var data Data
if err != nil {
return data, err
}
defer res.Body.Close()
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return data, err
}
return data, nil
}
参考
Goブログ JSON and Go
おすすめ図書
Tour of Goを終えた人にオススメです!!