Goブログ
JSONとGo
はじめに
JSON(JavaScript Object Notation)は、シンプルなデータ交換フォーマットです。構文的には、JavaScriptのオブジェクトとリストに似ています。最も一般的には、Webバックエンドとブラウザで実行されている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[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データの構造を事前に知らない場合はどうでしょうか?
インターフェースを使用した汎用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パッケージは、map[string]interface{}
と[]interface{}
の値を使用して、任意のJSONオブジェクトと配列を格納します。任意の有効な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のスライス:使用方法と内部構造
ブログインデックス