Goブログ

JSONとGo

Andrew Gerrand
2011年1月25日

はじめに

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)

すべてがうまくいけば、errnilになり、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データの[]bytemへのポインタを渡して、json.Unmarshalを呼び出します。

err := json.Unmarshal(b, &m)

bmに収まる有効なJSONが含まれている場合、呼び出し後、errnilになり、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を割り当てて設定します。そうでない場合、Barnilポインタのままになります。

ここから役立つパターンが生まれます。いくつかの異なるメッセージ型を受け取るアプリケーションがある場合、「受信者」構造を次のように定義できます。

type IncomingMessage struct {
    Cmd *Command
    Msg *Message
}

送信側は、伝えたいメッセージの種類に応じて、最上位のJSONオブジェクトのCmdフィールドと/またはMsgフィールドを設定できます。Unmarshalは、JSONをIncomingMessage構造体にデコードする際に、JSONデータに存在するデータ構造のみを割り当てます。どのメッセージを処理するかを知るには、プログラマはCmdまたはMsgnilではないかどうかをテストするだけです。

ストリーミングエンコーダとデコーダ

jsonパッケージは、JSONデータのストリームの読み書きという一般的な操作をサポートするために、DecoderEncoder型を提供します。NewDecoderNewEncoder関数は、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の遍在性により、これらのEncoderDecoder型は、HTTP接続、WebSockets、ファイルへの読み書きなど、幅広いシナリオで使用できます。

参照

詳細については、jsonパッケージのドキュメントを参照してください。jsonの使用方法の例については、jsonrpcパッケージのソースファイルを参照してください。

次の記事:Goがより安定しました
前の記事:Goのスライス:使用方法と内部構造
ブログインデックス