Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 17729] ファイルの概要

このコミットは、Go言語の標準ライブラリ encoding/json パッケージに json.RawMessage の使用例を追加するものです。具体的には、src/pkg/encoding/json/example_test.go ファイルに ExampleRawMessage という新しいテスト関数が追加され、JSONメッセージの一部を遅延解析する方法が示されています。

コミット

encoding/json パッケージに RawMessage の使用例を追加。

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0857045818d7cd31edfa54dec2c2b88af665c87c

元コミット内容

commit 0857045818d7cd31edfa54dec2c2b88af665c87c
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date:   Thu Oct 3 08:52:18 2013 +1000

    encoding/json: add an example for RawMessage
    
    RawMessage is useful and mildly non-obvious.
    Given the frequency with which RawMessage questions
    show up on golang-nuts, and get answered with an example,
    I suspect adding an example to the docs might help.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/14190044

変更の背景

このコミットの背景には、Go言語の公式メーリングリストである golang-nutsjson.RawMessage の使い方に関する質問が頻繁に寄せられていたという事実があります。json.RawMessage はJSONの柔軟な処理を可能にする強力な機能ですが、その使用方法は直感的ではない部分があり、多くの開発者がその適切な利用方法について疑問を抱いていました。

コミットメッセージにもあるように、「RawMessage は有用であり、やや自明ではない」という認識がありました。そのため、ドキュメントに具体的な使用例を追加することで、開発者がこの型をより容易に理解し、活用できるようになることが期待されました。これにより、同様の質問が繰り返されることを減らし、Goコミュニティ全体の生産性向上に貢献することを目的としています。

前提知識の解説

JSON (JavaScript Object Notation)

JSONは、人間が読み書きしやすく、機械が解析しやすいデータ交換フォーマットです。JavaScriptのオブジェクトリテラルをベースにしており、キーと値のペアの集合(オブジェクト)や、値の順序付きリスト(配列)でデータを表現します。Web APIのデータ送受信や設定ファイルの記述など、幅広い用途で利用されています。

Go言語の encoding/json パッケージ

Go言語の標準ライブラリには、JSONデータのエンコード(Goのデータ構造からJSON文字列への変換)とデコード(JSON文字列からGoのデータ構造への変換)を行うための encoding/json パッケージが用意されています。このパッケージは、Goの構造体とJSONオブジェクト間のマッピングを自動的に行う機能(マーシャリングとアンマーシャリング)を提供し、GoプログラムでJSONを扱うことを非常に容易にしています。

主な関数・型:

  • json.Marshal(): Goの値をJSON形式にエンコードします。
  • json.Unmarshal(): JSONデータをGoの値にデコードします。
  • json.Encoder, json.Decoder: ストリームからのJSONの読み書きを効率的に行います。
  • json.RawMessage: このコミットの主題となる型です。

json.RawMessage

json.RawMessage は、[]byte 型のエイリアスであり、JSONの生データをバイトスライスとして保持するために使用されます。この型の最も重要な特徴は、json.Unmarshaljson.RawMessage 型のフィールドを見つけると、そのフィールドに対応するJSONデータを解析せずにそのままバイトスライスとして保持するという点です。

通常、json.Unmarshal はJSONデータをGoの構造体にデコードする際に、すべてのフィールドをその型に応じて解析します。しかし、json.RawMessage を使用すると、特定のJSONサブツリーの解析を遅延させることができます。これは以下のようなシナリオで非常に有用です。

  1. 動的なスキーマ: JSONデータの一部が、他のフィールドの値によって異なるスキーマを持つ場合。例えば、type フィールドの値に応じて、data フィールドの構造が変化するようなケースです。RawMessage を使って data フィールドを一旦生データとして保持し、type フィールドを解析した後に、適切な構造体に data フィールドを再度アンマーシャルすることができます。
  2. パフォーマンス最適化: JSONデータの一部が常に必要とされるわけではない場合や、非常に大きなデータである場合。必要な時まで解析を遅延させることで、初期のデコード処理のオーバーヘッドを削減できます。
  3. 未知のフィールドの保持: JSONデータに、プログラムが直接扱わないが、後で別のサービスに転送する必要がある未知のフィールドが含まれている場合。RawMessage を使ってそれらのフィールドを保持し、元の形式を維持したまま転送できます。

json.RawMessagejson.Marshaler および json.Unmarshaler インターフェースを実装しており、これによりJSONのエンコード/デコードプロセスにシームレスに統合されます。

技術的詳細

このコミットで追加された ExampleRawMessage は、json.RawMessage の典型的なユースケースである「JSONメッセージの一部を遅延解析する」方法を具体的に示しています。

例では、Color という構造体が定義されており、その Point フィールドが json.RawMessage 型として宣言されています。

type Color struct {
	Space string
	Point json.RawMessage // delay parsing until we know the color space
}

この Color 構造体は、Space フィールド(色空間を示す文字列、例: "RGB", "YCbCr")と、Point フィールド(色空間に応じた座標データ)を持ちます。Point フィールドが json.RawMessage であるため、最初の json.Unmarshal 呼び出しでは、Point の内容はJSONオブジェクトとして解析されず、単なるバイトスライスとして Point フィールドに格納されます。

提供されるJSONデータは以下のようになっています。

[
	{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
	{"Space": "RGB",   "Point": {"R": 98, "G": 218, "B": 255}}
]

このJSON配列を []Color にアンマーシャルした後、プログラムは各 Color エントリをループ処理します。ループ内で、c.Space の値("RGB" または "YCbCr")に基づいて、Point フィールドの実際の構造を決定します。

	for _, c := range colors {
		var dst interface{}
		switch c.Space {
		case "RGB":
			dst = new(RGB)
		case "YCbCr":
			dst = new(YCbCr)
		}
		err := json.Unmarshal(c.Point, dst) // ここで遅延解析が行われる
		if err != nil {
			log.Fatalln("error:", err)
		}
		fmt.Println(c.Space, dst)
	}

switch 文の中で、c.Space の値に応じて RGB または YCbCr 型の新しいインスタンスが作成され、そのインスタンスに c.Pointjson.RawMessage 型の生データ)が再度 json.Unmarshal されます。これにより、Point フィールドの実際のデータが、その色空間に応じた適切なGoの構造体(RGB または YCbCr)にデコードされます。

このアプローチにより、JSONデータが動的なスキーマを持つ場合でも、柔軟かつ効率的に処理することが可能になります。最初のアンマーシャルでは共通の構造(SpacePoint の生データ)のみを処理し、その後の処理で具体的なスキーマに基づいて詳細な解析を行うことで、堅牢なJSONデコードロジックを構築できます。

コアとなるコードの変更箇所

src/pkg/encoding/json/example_test.go ファイルに、ExampleRawMessage という新しいテスト関数が追加されました。

--- a/src/pkg/encoding/json/example_test.go
+++ b/src/pkg/encoding/json/example_test.go
@@ -81,3 +81,49 @@ func ExampleDecoder() {
 	// Sam: Go fmt who?
 	// Ed: Go fmt yourself!
 }\n+\n+// This example uses RawMessage to delay parsing part of a JSON message.
+func ExampleRawMessage() {\n+\ttype Color struct {\n+\t\tSpace string
+\t\tPoint json.RawMessage // delay parsing until we know the color space
+\t}\n+\ttype RGB struct {\n+\t\tR uint8
+\t\tG uint8
+\t\tB uint8
+\t}\n+\ttype YCbCr struct {\n+\t\tY  uint8
+\t\tCb int8
+\t\tCr int8
+\t}\n+\n+\tvar j = []byte(`[\n+\t\t{\"Space\": \"YCbCr\", \"Point\": {\"Y\": 255, \"Cb\": 0, \"Cr\": -10}},\n+\t\t{\"Space\": \"RGB\",   \"Point\": {\"R\": 98, \"G\": 218, \"B\": 255}}\n+\t]`)\n+\tvar colors []Color\n+\terr := json.Unmarshal(j, &colors)\n+\tif err != nil {\n+\t\tlog.Fatalln("error:", err)\n+\t}\n+\n+\tfor _, c := range colors {\n+\t\tvar dst interface{}\n+\t\tswitch c.Space {\n+\t\tcase "RGB":\n+\t\t\tdst = new(RGB)\n+\t\tcase "YCbCr":\n+\t\t\tdst = new(YCbCr)\n+\t\t}\n+\t\terr := json.Unmarshal(c.Point, dst)\n+\t\tif err != nil {\n+\t\t\tlog.Fatalln("error:", err)\n+\t\t}\n+\t\tfmt.Println(c.Space, dst)\n+\t}\n+\t// Output:\n+\t// YCbCr &{255 0 -10}\n+\t// RGB &{98 218 255}\n+}\n```

## コアとなるコードの解説

追加された `ExampleRawMessage` 関数は、`json.RawMessage` を用いたJSONの遅延解析の具体的な手順を示しています。

1.  **構造体の定義**:
    *   `Color` 構造体: `Space` フィールド(文字列)と `Point` フィールド(`json.RawMessage` 型)を持ちます。`Point` が `json.RawMessage` であるため、このフィールドのJSONデータは最初のアンマーシャル時には解析されず、生データとして保持されます。
    *   `RGB` 構造体: `R`, `G`, `B` の各色成分(`uint8`)を持ちます。
    *   `YCbCr` 構造体: `Y`, `Cb`, `Cr` の各色成分(`uint8`, `int8`, `int8`)を持ちます。

2.  **JSONデータの準備**:
    *   `j` というバイトスライスに、`Color` 構造体に対応するJSONデータが定義されています。このデータは、`Space` フィールドの値によって `Point` フィールドの構造が異なることを示しています。

3.  **最初のアンマーシャル**:
    *   `json.Unmarshal(j, &colors)`: `j` のJSONデータを `[]Color` 型のスライス `colors` にアンマーシャルします。この段階では、`colors` 内の各 `Color` 要素の `Point` フィールドには、対応するJSONオブジェクトの生データがバイトスライスとして格納されます。

4.  **遅延解析の実行**:
    *   `for _, c := range colors`: `colors` スライス内の各 `Color` 要素をループ処理します。
    *   `switch c.Space`: 各 `Color` 要素の `Space` フィールドの値に基づいて、`Point` フィールドの実際の型を判断します。
    *   `dst = new(RGB)` または `dst = new(YCbCr)`: `Space` の値に応じて、適切な型の新しいインスタンスを作成し、`dst` インターフェース変数に代入します。
    *   `err := json.Unmarshal(c.Point, dst)`: ここが `json.RawMessage` の核心部分です。`c.Point` に格納されている生データ(`json.RawMessage` 型)を、`dst` が指す具体的な構造体(`RGB` または `YCbCr`)に再度アンマーシャルします。これにより、`Point` フィールドのデータが、その色空間に応じた正しいGoの構造体に完全に解析されます。
    *   `fmt.Println(c.Space, dst)`: 解析された色空間と座標データを出力します。

5.  **期待される出力**:
    *   `// Output:` コメントは、このテスト関数が実行された際に期待される標準出力の内容を示しています。これにより、`json.RawMessage` を用いた遅延解析が正しく機能していることが確認できます。

この例は、`json.RawMessage` が動的なJSONスキーマを扱う際にいかに強力であるかを明確に示しており、Go開発者がこの機能を理解し、自身のアプリケーションで活用するための貴重なリファレンスとなります。

## 関連リンク

*   Go言語 `encoding/json` パッケージのドキュメント: [https://pkg.go.dev/encoding/json](https://pkg.go.dev/encoding/json)
*   `json.RawMessage` のドキュメント: [https://pkg.go.dev/encoding/json#RawMessage](https://pkg.go.dev/encoding/json#RawMessage)
*   Go言語の公式メーリングリスト `golang-nuts`: [https://groups.google.com/g/golang-nuts](https://groups.google.com/g/golang-nuts)

## 参考にした情報源リンク

*   コミット情報: `/home/orange/Project/comemo/commit_data/17729.txt`
*   GitHubコミットページ: [https://github.com/golang/go/commit/0857045818d7cd31edfa54dec2c2b88af665c87c](https://github.com/golang/go/commit/0857045818d7cd31edfa54dec2c2b88af665c87c)
*   Go言語 `encoding/json` パッケージの公式ドキュメント
*   `json.RawMessage` の使用例に関する一般的なGoコミュニティの議論 (golang-nutsなど)