The Go Blog
JSONとGo
はじめに
JSON(JavaScript Object Notation)は、シンプルなデータ交換フォーマットです。構文的にはJavaScriptのオブジェクトとリストに似ています。ウェブのバックエンドとブラウザで実行されるJavaScriptプログラム間の通信に最も一般的に使用されますが、他の多くの場所でも使用されています。そのホームページであるjson.orgは、標準の非常に明確で簡潔な定義を提供しています。
jsonパッケージを使用すると、GoプログラムからJSONデータを簡単に読み書きできます。
エンコーディング
JSONデータをエンコードするには、Marshal関数を使用します。
func Marshal(v interface{}) ([]byte, error)
Goデータ構造Messageと、
type Message struct {
Name string
Body string
Time int64
}
Messageのインスタンスが与えられた場合、
m := Message{"Alice", "Hello", 1294706395881547000}
json.Marshalを使用して、mのJSONエンコードバージョンをマーシャルできます。
b, err := json.Marshal(m)
すべてがうまくいけば、errはnilになり、bは、このJSONデータを含む[]byteになります。
b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)
有効なJSONとして表現できるデータ構造のみがエンコードされます。
-
JSONオブジェクトはキーとして文字列のみをサポートします。Goのmap型をエンコードするには、
map[string]Tの形式である必要があります(ここでTはjsonパッケージがサポートする任意のGo型)。 -
チャネル、複素数、および関数型はエンコードできません。
-
循環データ構造はサポートされていません。これらは
Marshalを無限ループに陥らせます。 -
ポインタは、それが指す値としてエンコードされます(ポインタが
nilの場合は'null')。
jsonパッケージは、構造体型のエクスポートされたフィールド(大文字で始まるフィールド)にのみアクセスします。したがって、構造体のエクスポートされたフィールドのみがJSON出力に存在します。
デコーディング
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になり、bからのデータは、あたかも次のような代入によってかのように、構造体mに格納されます。
m = Message{
Name: "Alice",
Body: "Hello",
Time: 1294706395881547000,
}
Unmarshalは、デコードされたデータを格納するフィールドをどのように識別するのでしょうか?与えられたJSONキー"Foo"に対して、Unmarshalは、宛先構造体のフィールドを調べて、以下の順序で(優先順位順に)見つけます。
-
タグが
"Foo"のエクスポートされたフィールド(構造体タグの詳細についてはGo仕様を参照)。 -
名前が
"Foo"のエクスポートされたフィールド、または -
名前が
"FOO"、"FoO"、または"Foo"の他の大文字小文字を区別しない一致のエクスポートされたフィールド。
JSONデータの構造がGoの型と正確に一致しない場合はどうなるでしょうか?
b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
var m Message
err := json.Unmarshal(b, &m)
Unmarshalは、宛先型で見つけられるフィールドのみをデコードします。この場合、mのNameフィールドのみが設定され、Foodフィールドは無視されます。この動作は、大きなJSONブロブから特定のいくつかのフィールドのみを選択したい場合に特に便利です。また、宛先構造体内のエクスポートされていないフィールドはUnmarshalの影響を受けないことも意味します。
しかし、JSONデータの構造を事前に知らない場合はどうでしょうか?
interfaceによる汎用JSON
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 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{}値に問題なくアンマーシャルします。デフォルトの具象Go型は次のとおりです。
-
JSONブール値の場合は
bool、 -
JSON数値の場合は
float64、 -
JSON文字列の場合は
string、そして -
JSON nullの場合は
nil。
任意のデータのデコード
変数bに格納されたこのJSONデータを考えてみましょう。
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
このデータの構造を知らなくても、Unmarshalでinterface{}値にデコードできます。
var f interface{}
err := json.Unmarshal(b, &f)
この時点では、f内のGo値は、キーが文字列で、値自体が空のインターフェース値として格納されているマップになります。
f = map[string]interface{}{
"Name": "Wednesday",
"Age": 6,
"Parents": []interface{}{
"Gomez",
"Morticia",
},
}
このデータにアクセスするには、型アサーションを使用してfの基となるmap[string]interface{}にアクセスできます。
m := f.(map[string]interface{})
その後、rangeステートメントでマップを反復処理し、型スイッチを使用してその値を具体的な型としてアクセスできます。
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データを操作することができます。
参照型
前の例のデータを含む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ポインタとして残されます。
ここから有用なパターンが生まれます。いくつかの異なるメッセージタイプを受け取るアプリケーションがある場合、次のような「レシーバー」構造体を定義することができます。
type IncomingMessage struct {
Cmd *Command
Msg *Message
}
そして、送信側は、伝えたいメッセージの種類に応じて、トップレベルのJSONオブジェクトのCmdフィールドおよび/またはMsgフィールドを設定できます。Unmarshalは、JSONをIncomingMessage構造体にデコードする際に、JSONデータに存在するデータ構造のみを割り当てます。処理するメッセージを知るために、プログラマは単にCmdまたはMsgのいずれかがnilではないことをテストするだけで済みます。
ストリーミングエンコーダーとデコーダー
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)
}
}
}
ReaderとWriterの普及により、これらのEncoderとDecoder型は、HTTP接続、WebSockets、ファイルへの読み書きなど、幅広いシナリオで使用できます。
参考文献
詳細については、jsonパッケージのドキュメントを参照してください。jsonの使用例については、jsonrpcパッケージのソースファイルを参照してください。
次の記事:Goがより安定する
前の記事:Goスライス:使用法と内部構造
ブログインデックス