[インデックス 10816] ファイルの概要
コミット
- コミットハッシュ:
f89b5746fb809bef08eec46760ce429f420435fa
- 作者: Brad Fitzpatrick bradfitz@golang.org
- 日付: Thu Dec 15 10:02:47 2011 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f89b5746fb809bef08eec46760ce429f420435fa
元コミット内容
json: some tests to demonstrate bad error messages
Not a fix yet (help wanted), but part of Issue 2331
R=rsc
CC=golang-dev
https://golang.org/cl/5490043
変更の背景
このコミットは、Go言語の標準ライブラリであるencoding/json
パッケージにおける、JSONデコード時のエラーメッセージの品質に関する問題提起を目的としています。具体的には、json
タグの,string
オプションが誤用された場合に、ユーザーにとって理解しにくい、あるいは誤解を招くようなエラーメッセージが生成される現状をテストコードとして明確に示しています。
これは、GoのIssue 2331「json.unmarshal unmarshals an invalid json in some cases」の一部として行われたもので、直接的な修正ではなく、問題の再現と可視化に焦点を当てています。開発者は、このテストを通じて、より有用なエラーメッセージの必要性を強調し、コミュニティからの改善協力を求めています。
前提知識の解説
Go言語のencoding/json
パッケージ
encoding/json
パッケージは、GoプログラムとJSONデータの間で変換(エンコードとデコード)を行うための標準ライブラリです。Goの構造体とJSONオブジェクトの間でデータをマッピングする機能を提供し、WebアプリケーションやAPI開発において広く利用されています。
- エンコード (Marshal): Goのデータ構造(構造体、スライス、マップなど)をJSON形式のバイト列に変換します。
- デコード (Unmarshal): JSON形式のバイト列をGoのデータ構造に変換します。この際、
json.Unmarshal
関数やjson.Decoder
型が使用されます。
json
構造体タグと,string
オプション
Goの構造体フィールドには、json:"fieldName,options"
のようなタグを付与することで、JSONとのマッピング方法をカスタマイズできます。
fieldName
: JSONにおけるフィールド名を指定します。options
: 追加のデコード/エンコードオプションを指定します。,string
: このオプションは、通常、JSONの数値やブール値をGoの文字列型フィールドにデコードする際に使用されます。例えば、JSONの{"value": 123}
をGoのstruct { Value string
json:"value,string"}
にデコードすると、Value
フィールドには文字列"123"
が格納されます。このオプションは、JSONのデータ型とGoのフィールド型が直接一致しない場合に、型変換を試みるためのヒントとして機能します。しかし、このコミットが示すように、string
型のフィールドにstring
型のJSON値をデコードする際に,string
オプションを付与すると、意図しない挙動や誤解を招くエラーが発生する可能性があります。
JSONデコード時のエラーハンドリング
encoding/json
パッケージは、不正なJSON入力や型不一致が発生した場合にエラーを返します。理想的なエラーメッセージは、問題の原因を明確に示し、開発者が迅速に問題を特定し修正できるようにするものです。しかし、複雑なデコードシナリオや特定のタグの誤用においては、エラーメッセージが抽象的であったり、根本原因を指し示さない場合があります。
Goのテストフレームワーク (testing
パッケージ)
Goには、標準でtesting
パッケージが提供されており、ユニットテストやベンチマークテストを記述できます。
- テスト関数の命名:
TestXxx
という形式で関数名を定義します(例:TestErrorMessageFromMisusedString
)。 *testing.T
: テスト関数に渡される引数で、テストの失敗を報告したり、ログを出力したりするためのメソッドを提供します。t.Errorf(...)
: テストが失敗したことを報告し、指定されたフォーマットでエラーメッセージを出力します。テストの実行は継続されます。fmt.Sprintf
: フォーマットされた文字列を生成するために使用されます。このコミットでは、実際のエラーメッセージを文字列として取得し、期待されるエラーメッセージと比較するために使用されています。
技術的詳細
このコミットは、encoding/json
パッケージのDecoder.Decode
メソッドが、特定の不正なJSON入力に対して生成するエラーメッセージの不備を浮き彫りにしています。
GoのIssue 2331は、「json.unmarshal unmarshals an invalid json in some cases」と題されており、json.Decoder
の動作特性に起因する問題が指摘されています。json.Decoder
はストリーミングJSONデータを処理するように設計されており、入力ストリームから有効なJSON値を見つけると、その値をデコードして返します。この特性のため、入力ストリームの残りの部分が不正であっても、最初の有効なJSON値がデコードされてしまうことがあります。これは、encoding/json
パッケージ自体のバグというよりは、Decoder
の設計上の挙動であり、堅牢なエラーハンドリングが求められる場合には、開発者がこの特性を考慮してコードを記述する必要があります。
このコミットでは、特にjson:"result,string"
というタグをstring
型のフィールドに適用した場合の挙動に焦点を当てています。通常、,string
オプションは数値やブール値を文字列としてデコードするために使用されますが、既にstring
型であるフィールドに適用すると、デコーダは予期しない内部状態に陥り、結果として「JSON decoder out of sync - data changing underfoot?」のような、ユーザーにとって意味不明なエラーメッセージを生成することがあります。また、JSONの型(数値やブール値)がGoのstring
型フィールドにデコードされる際に、",string"
オプションが指定されていない場合と同様に型不一致エラーが発生しますが、そのメッセージも必ずしも直感的ではありません。
このコミットで追加されたテストは、これらのシナリオを具体的に再現し、現状のエラーメッセージがどれほど不親切であるかを示しています。これは、将来的にencoding/json
パッケージのエラーメッセージを改善するための基礎データとなります。
コアとなるコードの変更箇所
変更はsrc/pkg/encoding/json/decode_test.go
ファイルに対して行われています。
--- a/src/pkg/encoding/json/decode_test.go
+++ b/src/pkg/encoding/json/decode_test.go
@@ -6,6 +6,7 @@ package json
import (
"bytes"
+ "fmt"
"reflect"
"strings"
"testing"
@@ -242,6 +243,38 @@ func TestHTMLEscape(t *testing.T) {
}\n}\n\n+// WrongString is a struct that's misusing the ,string modifier.
+type WrongString struct {
+ Message string `json:"result,string"`
+}\n+\n+type wrongStringTest struct {
+ in, err string
+}\n+\n+// TODO(bradfitz): as part of Issue 2331, fix these tests' expected
+// error values to be helpful, rather than the confusing messages they
+// are now.
+var wrongStringTests = []wrongStringTest{
+ {`{"result":"x"}`, "JSON decoder out of sync - data changing underfoot?"},
+ {`{"result":"foo"}`, "json: cannot unmarshal bool into Go value of type string"},
+ {`{"result":"123"}`, "json: cannot unmarshal number into Go value of type string"},
+}\n+\n+// If people misuse the ,string modifier, the error message should be
+// helpful, telling the user that they're doing it wrong.
+func TestErrorMessageFromMisusedString(t *testing.T) {
+ for n, tt := range wrongStringTests {
+ r := strings.NewReader(tt.in)
+ var s WrongString
+ err := NewDecoder(r).Decode(&s)
+ got := fmt.Sprintf("%v", err)
+ if got != tt.err {
+ t.Errorf("%d. got err = %q, want %q", n, got, tt.err)
+ }
+ }
+}\n+\n func noSpace(c rune) rune {
if isSpace(c) {
return -1
主な変更点は以下の通りです。
fmt
パッケージのインポート: エラーメッセージをフォーマットするためにfmt
パッケージが追加されました。WrongString
構造体の定義:type WrongString struct { Message string `json:"result,string"` }
Message
フィールドがstring
型であるにもかかわらず、json:"result,string"
タグが付与されています。これが誤用を意図した構造体です。wrongStringTest
構造体とwrongStringTests
スライスの定義:type wrongStringTest struct { in, err string } var wrongStringTests = []wrongStringTest{ {`{"result":"x"}`, "JSON decoder out of sync - data changing underfoot?"}, {`{"result":"foo"}`, "json: cannot unmarshal bool into Go value of type string"}, {`{"result":"123"}`, "json: cannot unmarshal number into Go value of type string"}, }
in
は入力JSON文字列、err
は期待される(現状の)エラーメッセージです。異なる不正な入力パターンと、それに対応するエラーメッセージが定義されています。TestErrorMessageFromMisusedString
関数の追加:
このテスト関数はfunc TestErrorMessageFromMisusedString(t *testing.T) { for n, tt := range wrongStringTests { r := strings.NewReader(tt.in) var s WrongString err := NewDecoder(r).Decode(&s) got := fmt.Sprintf("%v", err) if got != tt.err { t.Errorf("%d. got err = %q, want %q", n, got, tt.err) } } }
wrongStringTests
スライスをループし、各テストケースに対して以下の処理を行います。strings.NewReader(tt.in)
で入力JSON文字列からio.Reader
を作成します。NewDecoder(r).Decode(&s)
でJSONデコードを試みます。fmt.Sprintf("%v", err)
でエラーオブジェクトを文字列に変換します。- 変換されたエラー文字列
got
が、期待されるエラー文字列tt.err
と一致しない場合にt.Errorf
でテスト失敗を報告します。
コアとなるコードの解説
このコミットの核心は、TestErrorMessageFromMisusedString
テスト関数とその関連データ構造にあります。
WrongString
構造体は、json:"result,string"
タグがstring
型のフィールドMessage
に適用されているという、意図的な「誤用」を示しています。通常、,string
オプションはJSONの数値やブール値をGoの文字列としてデコードするために使われます。しかし、JSONのstring
値をGoのstring
フィールドにデコードする際にこのオプションを使うと、デコーダの内部ロジックが混乱し、予期しないエラーパスに分岐することがあります。
wrongStringTests
スライスは、この誤用が引き起こす具体的なエラーシナリオを網羅しています。
{
"result":"x"}
,"JSON decoder out of sync - data changing underfoot?"}
:- JSONの
"x"
は有効な文字列ですが、,string
オプションの誤用により、デコーダが内部状態を失い、「データが足元で変化している」というような、非常に抽象的で役に立たないエラーメッセージを生成するケースです。これは、デコーダが期待する形式(数値やブール値の文字列表現)ではない文字列を受け取った際に発生する可能性のある、内部的な不整合を示唆しています。
- JSONの
{
"result":"foo"}
,"json: cannot unmarshal bool into Go value of type string"}
:- JSONの
"foo"
は文字列ですが、デコーダが,string
オプションを解釈しようとする際に、内部的にブール値への変換を試み、それが失敗したかのようなエラーメッセージを生成しています。これは、,string
オプションが数値やブール値のデコードに特化しているため、文字列リテラルに対して適用された場合に、デコーダが誤った型変換ロジックを適用しようとする結果と考えられます。
- JSONの
{
"result":"123"}
,"json: cannot unmarshal number into Go value of type string"}
:- JSONの
"123"
は文字列ですが、デコーダがこれを数値として解釈しようとし、Goのstring
型フィールドに数値としてアンマーシャルできないというエラーを返しています。これは、,string
オプションが数値の文字列表現を期待しているにもかかわらず、JSONの文字列リテラルが直接渡された場合に、デコーダがその文字列を数値としてパースしようとする挙動を示唆しています。
- JSONの
TestErrorMessageFromMisusedString
関数は、これらのテストケースを自動的に実行し、実際のエラーメッセージがwrongStringTests
で定義された「期待される(現状の)エラーメッセージ」と一致するかを確認します。もし一致しない場合、テストは失敗し、開発者はエラーメッセージの変更を検知できます。
このテストの目的は、これらのエラーメッセージが「helpful(役に立つ)」ではないことを明確に示し、GoのIssue 2331で議論されているように、より診断的でユーザーフレンドリーなエラーメッセージへの改善を促すことです。TODO(bradfitz)
コメントは、このテストが修正ではなく、問題の特定と将来の改善のためのものであることを明示しています。
関連リンク
- Gerrit Code Review: https://golang.org/cl/5490043
- Go Issue 2331: https://golang.org/issue/2331
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEfXtqSK3mcqUJ3kEaXmSy8wxSq-l1NZsunh5L6zj-MF4HAApremCxToYLwSqp_jOLSu5rxmcJziF50aZZtxrf44qur093WrzRxKZnzXm2QTRRXnZAikTBBrKZfrtiGtAyDuxSzIiZtCy2GPCQ69y8=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGTMx1p9WJQHibY6Y8aacK47uK21htBu7Yf1cEFXIE_A0Dr4c3l4YdUGZ5DIouhcroywEoIzKt9SdT5F_HdwwyU-BA3lhJnKR5-qg-leYwRMf_aeGwUrItL4nrBFLaWzfx1mQpSP9t_YHS8sXgIor0hCDhSOeTVfWuzfAmsgCInUTm1Q66OvmcuMsxUIKyCPg==