はじめに

JSON (JavaScript Object Notation) は、シンプルなデータ交換フォーマットです。構文的には、JavaScript のオブジェクトやリストに似ています。主にウェブバックエンドとブラウザで実行される JavaScript プログラム間の通信に使用されますが、他の多くの場所でも使用されています。ホームページ json.org では、標準の明確で簡潔な定義が提供されています。

json パッケージ を使用すると、Go プログラムから JSON データを簡単に読み書きできます。

エンコーディング

JSON データをエンコードするには、Marshal 関数を使用します。

  1. func Marshal(v interface{}) ([]byte, error)

Go のデータ構造 Message を考慮すると、

  1. type Message struct {
  2. Name string
  3. Body string
  4. Time int64
  5. }

および Message のインスタンスがある場合、

  1. m := Message{"Alice", "Hello", 1294706395881547000}

m の JSON エンコード版を json.Marshal を使用してマシャルできます:

  1. b, err := json.Marshal(m)

すべてが正常であれば、errnil になり、b はこの JSON データを含む []byte になります:

  1. b == []byte(`{"Name":"Alice","Body":"Hello","Time":1294706395881547000}`)

有効な JSON として表現できるデータ構造のみがエンコードされます:

  • JSON オブジェクトは、キーとして文字列のみをサポートします。Go のマップ型をエンコードするには、map[string]T の形式でなければなりません(T は json パッケージでサポートされている任意の Go 型です)。
  • チャネル、複素数、関数型はエンコードできません。
  • 循環データ構造はサポートされていません。これにより、Marshal が無限ループに入ります。
  • ポインタは、指している値としてエンコードされます(または、ポインタが nil の場合は ‘null’ になります)。

json パッケージは、構造体型のエクスポートされたフィールド(大文字で始まるもの)のみをアクセスします。したがって、構造体のエクスポートされたフィールドのみが JSON 出力に含まれます。

デコーディング

JSON データをデコードするには、Unmarshal 関数を使用します。

  1. func Unmarshal(data []byte, v interface{}) error

まず、デコードされたデータを格納する場所を作成する必要があります。

  1. var m Message

次に、json.Unmarshal を呼び出し、JSON データの []bytem へのポインタを渡します。

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

もし bm に収まる有効な JSON を含んでいる場合、呼び出し後に errnil になり、b からのデータは構造体 m に格納されます。これは、次のような代入によって行われます:

  1. m = Message{
  2. Name: "Alice",
  3. Body: "Hello",
  4. Time: 1294706395881547000,
  5. }

Unmarshal は、デコードされたデータを格納するフィールドをどのように特定しますか?特定の JSON キー "Foo" に対して、Unmarshal は、優先順位の順に、宛先構造体のフィールドを探します:

  • "Foo" のタグを持つエクスポートされたフィールド(構造体タグについては Go spec を参照)、
  • "Foo" という名前のエクスポートされたフィールド、または
  • "FOO" または "FoO" という名前のエクスポートされたフィールド、または "Foo" のケースインセンシティブな一致のいずれか。

JSON データの構造が Go 型と正確に一致しない場合はどうなりますか?

  1. b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
  2. var m Message
  3. err := json.Unmarshal(b, &m)

Unmarshal は、宛先型で見つけられるフィールドのみをデコードします。この場合、m の Name フィールドのみが populated され、Food フィールドは無視されます。この動作は、大きな JSON ブロブから特定のフィールドのみを選択したい場合に特に便利です。また、宛先構造体の未エクスポートフィールドは Unmarshal によって影響を受けません。

しかし、事前に JSON データの構造がわからない場合はどうなりますか?

インターフェースによる汎用 JSON

interface{}(空のインターフェース)型は、メソッドがゼロのインターフェースを説明します。すべての Go 型は少なくともゼロのメソッドを実装しているため、空のインターフェースを満たします。

空のインターフェースは一般的なコンテナ型として機能します:

  1. var i interface{}
  2. i = "a string"
  3. i = 2011
  4. i = 2.777

型アサーションは、基になる具体的な型にアクセスします:

  1. r := i.(float64)
  2. fmt.Println("the circle's area", math.Pi*r*r)

または、基になる型が不明な場合、型スイッチが型を決定します:

  1. switch v := i.(type) {
  2. case int:
  3. fmt.Println("twice i is", v*2)
  4. case float64:
  5. fmt.Println("the reciprocal of i is", 1/v)
  6. case string:
  7. h := len(v) / 2
  8. fmt.Println("i swapped by halves is", v[h:]+v[:h])
  9. default:
  10. // i isn't one of the types above
  11. }

json パッケージは、map[string]interface{} および []interface{} の値を使用して任意の JSON オブジェクトや配列を格納します。有効な JSON ブロブをプレーンな interface{} 値にデシリアライズすることができます。デフォルトの具体的な Go 型は次のとおりです:

  • JSON ブール値には bool
  • JSON 数値には float64
  • JSON 文字列には string、および
  • JSON null には nil

任意のデータのデコーディング

この JSON データを考えてみましょう。変数 b に格納されています:

  1. b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)

このデータの構造を知らなくても、interface{} 値に Unmarshal を使用してデコードできます:

  1. var f interface{}
  2. err := json.Unmarshal(b, &f)

この時点で、f の Go 値は、キーが文字列で、値が空のインターフェース値として格納されているマップになります:

  1. f = map[string]interface{}{
  2. "Name": "Wednesday",
  3. "Age": 6,
  4. "Parents": []interface{}{
  5. "Gomez",
  6. "Morticia",
  7. },
  8. }

このデータにアクセスするには、型アサーションを使用して f の基になる map[string]interface{} にアクセスできます:

  1. m := f.(map[string]interface{})

次に、範囲文を使用してマップを反復処理し、型スイッチを使用してその値に具体的な型としてアクセスできます:

  1. for k, v := range m {
  2. switch vv := v.(type) {
  3. case string:
  4. fmt.Println(k, "is string", vv)
  5. case float64:
  6. fmt.Println(k, "is float64", vv)
  7. case []interface{}:
  8. fmt.Println(k, "is an array:")
  9. for i, u := range vv {
  10. fmt.Println(i, u)
  11. }
  12. default:
  13. fmt.Println(k, "is of a type I don't know how to handle")
  14. }
  15. }

このようにして、未知の JSON データを扱いながら、型安全性の利点を享受できます。

参照型

前の例からのデータを含む Go 型を定義しましょう:

  1. type FamilyMember struct {
  2. Name string
  3. Age int
  4. Parents []string
  5. }
  6. var m FamilyMember
  7. err := json.Unmarshal(b, &m)

そのデータを FamilyMember 値にアンマーシャルすることは期待通りに機能しますが、よく見ると驚くべきことが起こったことがわかります。var ステートメントを使用して FamilyMember 構造体を割り当て、その値へのポインタを Unmarshal に提供しましたが、その時点で Parents フィールドは nil スライス値でした。Parents フィールドを populated するために、Unmarshal は裏で新しいスライスを割り当てました。これは、Unmarshal がサポートされている参照型(ポインタ、スライス、マップ)でどのように機能するかの典型的な例です。

このデータ構造にアンマーシャルすることを考えてみましょう:

  1. type Foo struct {
  2. Bar *Bar
  3. }

JSON オブジェクトに Bar フィールドがある場合、Unmarshal は新しい Bar を割り当てて populated します。そうでない場合、Barnil ポインタのままになります。

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

  1. type IncomingMessage struct {
  2. Cmd *Command
  3. Msg *Message
  4. }

送信者は、伝えたいメッセージの型に応じて、トップレベルの JSON オブジェクトの Cmd フィールドおよび/または Msg フィールドを populated できます。Unmarshal は、JSON を IncomingMessage 構造体にデコードする際に、JSON データに存在するデータ構造のみを割り当てます。どのメッセージを処理するかを知るために、プログラマーは単に Cmd または Msgnil でないことをテストすればよいのです。

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

json パッケージは、JSON データのストリームを読み書きする一般的な操作をサポートするために、Decoder および Encoder 型を提供します。NewDecoder および NewEncoder 関数は、io.Reader および io.Writer インターフェース型をラップします。

  1. func NewDecoder(r io.Reader) *Decoder
  2. func NewEncoder(w io.Writer) *Encoder

標準入力から一連の JSON オブジェクトを読み取り、各オブジェクトから Name フィールド以外を削除し、オブジェクトを標準出力に書き込む例のプログラムは次のとおりです:

  1. package main
  2. import (
  3. "encoding/json"
  4. "log"
  5. "os"
  6. )
  7. func main() {
  8. dec := json.NewDecoder(os.Stdin)
  9. enc := json.NewEncoder(os.Stdout)
  10. for {
  11. var v map[string]interface{}
  12. if err := dec.Decode(&v); err != nil {
  13. log.Println(err)
  14. return
  15. }
  16. for k := range v {
  17. if k != "Name" {
  18. delete(v, k)
  19. }
  20. }
  21. if err := enc.Encode(&v); err != nil {
  22. log.Println(err)
  23. }
  24. }
  25. }

Reader と Writer の普及により、これらの Encoder および Decoder 型は、HTTP 接続、WebSocket、またはファイルへの読み書きなど、幅広いシナリオで使用できます。

参考文献

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