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

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

コミット

コミットハッシュ: 53523f6a7d6ca6aa6ec4a1d20af773f333284371
作成者: Russ Cox rsc@golang.org
日付: Mon Nov 14 16:03:23 2011 -0500
タイトル: encoding/json: decode [] as empty slice, not nil slice
変更ファイル: src/pkg/encoding/json/decode.go (4行追加)

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

https://github.com/golang/go/commit/53523f6a7d6ca6aa6ec4a1d20af773f333284371

元コミット内容

encoding/json: decode [] as empty slice, not nil slice

Test was already present, but bug in reflect.DeepEqual hid this bug.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5375090

変更内容:

  • src/pkg/encoding/json/decode.goarray関数に3行のコードを追加
  • 空のJSONアレイ[]をデコードする際に、nil sliceではなく空のsliceを作成するよう修正

変更の背景

このコミットは、Go言語のJSON パッケージにおける重要な一貫性の問題を修正したものです。2011年の時点で、Goのencoding/jsonパッケージは以下のような矛盾した振る舞いを持っていました:

  1. エンコード時: nil sliceはnullとして、空のslice([]string{})は[]としてエンコードされる
  2. デコード時: 空のJSONアレイ[]がnil sliceとしてデコードされる

この非対称性は、JSONのラウンドトリップ(エンコード→デコード)において予期しない結果を生み出していました。例えば:

// エンコード前
data := []string{}  // 空のslice(非nil)

// JSONエンコード
json := `[]`

// デコード後
var result []string // nil slice

この問題は、特にAPI設計において重要な意味を持ちます。多くのAPIでは、null[]は異なる意味を持つため、この一貫性の欠如は深刻な問題となっていました。

前提知識の解説

Goにおけるnil sliceと空のsliceの違い

Goでは、sliceには以下の2つの状態があります:

  1. nil slice: var s []string または s := []string(nil)

    • 内部的にはポインタがnilを指している
    • len(s) == 0cap(s) == 0s == niltrue
    • メモリ効率が良い(メモリ割り当てが不要)
  2. 空のslice: s := []string{} または s := make([]string, 0)

    • 内部的には有効なポインタを持つが、要素数が0
    • len(s) == 0cap(s) == 0s == nilfalse
    • わずかなメモリ使用量がある

JSONとsliceの関係

JSONにおいて:

  • nullは値の不在を表す
  • []は空のアレイを表す

これらは意味的に異なるものですが、Goのnil sliceと空のsliceの区別と自然に対応します。

reflect.DeepEqualの問題

コミットメッセージで言及されている「bug in reflect.DeepEqual hid this bug」について:

reflect.DeepEqualは、nil sliceと空のsliceを異なるものとして扱います:

var nilSlice []string
emptySlice := []string{}
fmt.Println(reflect.DeepEqual(nilSlice, emptySlice)) // false

この動作により、既存のテストでは空のJSONアレイをデコードした結果がnil sliceになっていても、テストが適切に失敗していませんでした。

技術的詳細

修正前の動作

修正前のコードでは、空のJSONアレイ[]をデコードすると、以下のような流れで処理されていました:

  1. array関数が呼び出される
  2. 配列要素の解析ループが実行される(要素数は0)
  3. ループ変数iは0のまま
  4. slice lengthの調整処理は実行されるが、nil sliceの場合は何も行われない
  5. 結果として、デコード先のsliceはnilのまま残る

修正後の動作

修正後は、以下の処理が追加されました:

if i == 0 && av.Kind() == reflect.Slice && sv.IsNil() {
    sv.Set(reflect.MakeSlice(sv.Type(), 0, 0))
}

この条件は以下をチェックします:

  • i == 0: 配列要素が0個だった
  • av.Kind() == reflect.Slice: 対象がslice型である
  • sv.IsNil(): 現在のsliceがnilである

これらの条件が満たされた場合、reflect.MakeSlice(sv.Type(), 0, 0)を使って長さ0、容量0の空のsliceを作成し、設定します。

リフレクションの詳細

この修正は、Goのリフレクション機能を使用して実装されています:

  • reflect.Value.Kind(): 値の種類(slice、array等)を取得
  • reflect.Value.IsNil(): 値がnilかどうかを判定
  • reflect.MakeSlice(typ, len, cap): 指定された型、長さ、容量のsliceを作成
  • reflect.Value.Set(): 値を設定

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

ファイル: src/pkg/encoding/json/decode.go
関数: array関数
行数: 32-34行目(追加された部分)

@@ -392,6 +393,9 @@ func (d *decodeState) array(v reflect.Value) {
 			sv.SetLen(i)
 		}
 	}
+	if i == 0 && av.Kind() == reflect.Slice && sv.IsNil() {
+		sv.Set(reflect.MakeSlice(sv.Type(), 0, 0))
+	}
 }

コアとなるコードの解説

修正されたarray関数の構造

array関数は、JSONアレイをGoのarray/sliceにデコードする処理を行います:

  1. 初期化フェーズ: 対象の型を確認し、array/sliceに応じた処理を準備
  2. 要素解析ループ: JSON内の各要素を順次デコード
  3. 長さ調整フェーズ: 必要に応じてsliceの長さを調整
  4. 空配列特別処理 (今回の修正): 空の配列の場合の特別な処理

追加されたコードの詳細解説

if i == 0 && av.Kind() == reflect.Slice && sv.IsNil() {
    sv.Set(reflect.MakeSlice(sv.Type(), 0, 0))
}

各条件の意味:

  1. i == 0:

    • 変数iは解析された配列要素の数を示す
    • 0の場合、空のJSONアレイ[]がデコードされたことを意味
  2. av.Kind() == reflect.Slice:

    • avreflect.Value型の変数で、デコード対象の型情報を持つ
    • この条件でslice型(array型ではない)であることを確認
  3. sv.IsNil():

    • svは実際の値を保持するreflect.Value
    • nilである場合、まだ実際のsliceインスタンスが作成されていない

実行される処理:

  • reflect.MakeSlice(sv.Type(), 0, 0):

    • 第1引数: sliceの型(元の型を保持)
    • 第2引数: 長さ(0)
    • 第3引数: 容量(0)
    • 戻り値: 新しい空のsliceのreflect.Value
  • sv.Set(...): 作成した空のsliceを実際の変数に設定

パフォーマンスへの影響

この修正は最小限のパフォーマンス影響で実装されています:

  1. 条件チェック: 3つの単純な条件チェックのみ
  2. slice作成: 空のsliceの作成は軽量な操作
  3. 実行頻度: 空のJSONアレイのデコード時のみ実行

関連リンク

参考にした情報源リンク