[インデックス 11138] ファイルの概要
json: better error messages when the ,string option is misused
コミット
コミットハッシュ: b37de7387a32a707dad0ef0305ec686bc263ef24
作者: Brad Fitzpatrick bradfitz@golang.org
日付: Thu Jan 12 14:40:29 2012 -0800
json: better error messages when the ,string option is misused
Fixes #2331
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5544045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b37de7387a32a707dad0ef0305ec686bc263ef24
元コミット内容
json: better error messages when the ,string option is misused
Fixes #2331
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5544045
変更の背景
Go言語の標準ライブラリであるencoding/json
パッケージにおいて、構造体タグの,string
オプションが誤用された際に、デコード時のエラーメッセージが不明瞭であったという問題(Go Issue 2331)が存在していました。
,string
オプションは、Goの構造体フィールドをJSONにエンコード/デコードする際に、そのフィールドの値をJSONの文字列として扱うことを指定します。例えば、Goのint
型のフィールドにjson:"age,string"
というタグを付けると、JSONでは"age": "30"
のように数値が文字列として表現されます。これは、JavaScriptなど、数値の精度に問題がある環境との相互運用性や、JSONの数値が時には文字列として送られてくるような柔軟なデータ形式に対応するために有用です。
しかし、この,string
オプションが不適切に使用された場合、例えばJSONの真偽値(true
/false
)をGoのstring
型にデコードしようとしたり、JSONの数値(123
)をGoのstring
型にデコードしようとしたりすると、json: cannot unmarshal bool into Go value of type string
のような一般的な型不一致エラーが発生していました。このエラーメッセージだけでは、開発者が問題の根本原因が,string
オプションの誤用にあることを即座に特定することが困難でした。
このコミットは、このような,string
オプションの誤用に対して、より具体的で分かりやすいエラーメッセージを提供することで、開発者がデバッグをより効率的に行えるようにすることを目的としています。
前提知識の解説
- Goの
encoding/json
パッケージ: Go言語の標準ライブラリの一部であり、JSONデータとGoの構造体(struct)の間でデータを変換(エンコード/デコード)するための機能を提供します。 - 構造体タグ(Struct Tags): Goの構造体のフィールド宣言に付与される文字列リテラルで、フィールドに関するメタデータを提供します。
encoding/json
パッケージでは、json:"fieldName,option"
のような形式で、JSONのキー名やエンコード/デコード時の挙動を制御するために広く利用されます。 ,string
オプション: 構造体タグのオプションの一つで、json:"key,string"
のように使用されます。このオプションが付与されたフィールドは、JSON上では常に文字列として扱われます。- エンコード時(Marshal): Goの数値型や真偽値型が、対応するJSON文字列として出力されます(例:
int(123)
が"123"
に、bool(true)
が"true"
に)。 - デコード時(Unmarshal): JSONの文字列値(例:
"123"
や"true"
)を、Goの対応する数値型や真偽値型に変換します。また、JSONの数値や真偽値そのもの(例:123
やtrue
)も、このオプションが付与されていればGoの対応する型に正しくデコードできる柔軟性も持ちます。 - 誤用の例: このオプションは、JSONのプリミティブ型(数値、真偽値、文字列)をGoの対応する型に、JSON文字列としてデコードすることを意図しています。例えば、JSONの
true
をGoのbool
にデコードする際に、"true"
というJSON文字列として受け取ることを期待するケースです。しかし、JSONのtrue
をGoのstring
型にデコードしようとするなど、型が根本的に異なる場合に問題が発生し、以前は不明瞭なエラーメッセージが返されていました。
- エンコード時(Marshal): Goの数値型や真偽値型が、対応するJSON文字列として出力されます(例:
- アンマーシャリング(Unmarshaling): JSON形式のデータをGoのプログラム内で扱えるデータ構造(通常は構造体やマップ)に変換するプロセスを指します。
UnmarshalTypeError
:encoding/json
パッケージが、JSONの値の型とGoの構造体フィールドの型が一致しない場合に生成するエラーの一種です。errPhase
:encoding/json
パッケージの内部で使用される、JSONのパース処理中に発生する一般的な構文エラーや予期せぬ状況を示すエラーです。
技術的詳細
このコミットの主要な変更は、src/pkg/encoding/json/decode.go
ファイル内のJSONデコードロジック、特にliteralStore
関数のエラーハンドリングの改善にあります。
-
literalStore
関数のシグネチャ変更:- 変更前:
func (d *decodeState) literalStore(item []byte, v reflect.Value)
- 変更後:
func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool)
- 新しく追加された
fromQuoted
引数は、現在処理しているJSONリテラルが、構造体タグの,string
オプションによってJSON文字列としてラップされたもの(例: JSONの"true"
をGoのbool
にデコードしようとしている場合)であるかどうかを示すブール値です。
- 変更前:
-
object
関数からの呼び出し箇所の変更:object
関数内で、JSONオブジェクトのフィールド値をデコードする際にliteralStore
が呼び出されます。- ここで、
destring
という内部変数がtrue
(これは、現在のフィールドに,string
オプションが適用されていることを示す)の場合に、literalStore
のfromQuoted
引数にtrue
を渡すように変更されました。これにより、literalStore
関数は、デコード中の値が,string
オプションの影響を受けているかどうかを認識できるようになります。
-
literalStore
内部でのエラーメッセージの改善:literalStore
関数内では、JSONリテラルの種類(真偽値、文字列、数値)に応じてGoの対応する型へのデコードを試みます。- デコードに失敗し、かつ
fromQuoted
がtrue
である場合(つまり、,string
オプションが適用されているにもかかわらず型変換に失敗した場合)、より具体的で分かりやすいエラーメッセージが生成されるようになりました。 - 具体的には、
fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())
という形式のエラーメッセージが使用されます。このメッセージは、どの値(%q
で表示されるitem
)をどのGoの型(%v
で表示されるv.Type()
)にアンマーシャルしようとして失敗したのか、そしてその原因が「,string
構造体タグの不正な使用」にあることを明確に示します。 fmt
パッケージが新しくインポートされています。
-
テストケースの更新:
src/pkg/encoding/json/decode_test.go
ファイル内のwrongStringTests
というテストケースが更新されました。- これらのテストは、
,string
オプションが誤用された場合に発生するエラーを検証するためのものです。 - コミット前は、期待されるエラーメッセージが一般的なものでしたが、コミット後は、上記の新しい詳細なエラーメッセージに更新されました。これにより、変更が正しく機能し、期待されるエラーメッセージが生成されることがテストによって保証されます。
-
ビルドスクリプトの変更:
- 多数の
src/buildscript_*.sh
ファイル(各OS/アーキテクチャ向けのビルドスクリプト)が変更されています。これらの変更は、reflect
、unicode/utf16
、encoding/json
といったパッケージのビルド順序や、ビルドプロセスにおける一時ファイルの生成・コピーのロジックが調整されたことによるものです。これは、JSONデコードロジック自体の変更とは直接関係ありませんが、Goのビルドシステム全体での依存関係の整理や最適化の一環として行われたものと推測されます。
- 多数の
コアとなるコードの変更箇所
-
src/pkg/encoding/json/decode.go
:import
セクションに"fmt"
が追加されました。func (d *decodeState) object(v reflect.Value)
関数内で、d.literalStore
の呼び出しが変更され、destring
の値に応じてfromQuoted
引数にtrue
またはfalse
が渡されるようになりました。--- a/src/pkg/encoding/json/decode.go +++ b/src/pkg/encoding/json/decode.go @@ -538,7 +539,7 @@ func (d *decodeState) object(v reflect.Value) { // Read value. if destring { d.value(reflect.ValueOf(&d.tempstr)) - d.literalStore([]byte(d.tempstr), subv) + d.literalStore([]byte(d.tempstr), subv, true) } else { d.value(subv) }
func (d *decodeState) literal(v reflect.Value)
関数内で、d.literalStore
の呼び出しが変更され、fromQuoted
引数にfalse
が渡されるようになりました。--- a/src/pkg/encoding/json/decode.go +++ b/src/pkg/encoding/json/decode.go @@ -571,11 +572,15 @@ func (d *decodeState) literal(v reflect.Value) { d.off-- d.scan.undo(op) - d.literalStore(d.data[start:d.off], v) + d.literalStore(d.data[start:d.off], v, false) } // literalStore decodes a literal stored in item into v. -func (d *decodeState) literalStore(item []byte, v reflect.Value) { +// +// fromQuoted indicates whether this literal came from unwrapping a +// string from the ",string" struct tag option. this is used only to +// produce more helpful error messages. +func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) { // Check for unmarshaler. wantptr := item[0] == 'n' // null unmarshaler, pv := d.indirect(v, wantptr)
literalStore
関数内で、真偽値、文字列、数値のデコード失敗時にfromQuoted
の値に応じてエラーメッセージが分岐するようになりました。--- a/src/pkg/encoding/json/decode.go +++ b/src/pkg/encoding/json/decode.go @@ -601,7 +606,11 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value) { value := c == 't' switch v.Kind() { default: - d.saveError(&UnmarshalTypeError{"bool", v.Type()}) + if fromQuoted { + d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.saveError(&UnmarshalTypeError{"bool", v.Type()}) + } case reflect.Bool: v.SetBool(value) case reflect.Interface: @@ -611,7 +620,11 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value) { case '"': // string s, ok := unquoteBytes(item) if !ok { - d.error(errPhase) + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } } switch v.Kind() { default: @@ -636,12 +649,20 @@ func (d *decodeState) literalStore(item []byte, v reflect.Value) { default: // number if c != '-' && (c < '0' || c > '9') { - d.error(errPhase) + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(errPhase) + } } s := string(item) switch v.Kind() { default: - d.error(&UnmarshalTypeError{"number", v.Type()}) + if fromQuoted { + d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) + } else { + d.error(&UnmarshalTypeError{"number", v.Type()}) + } case reflect.Interface: n, err := strconv.ParseFloat(s, 64) if err != nil {
-
src/pkg/encoding/json/decode_test.go
:wrongStringTests
内の期待されるエラーメッセージが更新されました。--- a/src/pkg/encoding/json/decode_test.go +++ b/src/pkg/encoding/json/decode_test.go @@ -258,13 +258,10 @@ type wrongStringTest struct { in, err string } -// 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"}, + {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, + {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, + {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, } // If people misuse the ,string modifier, the error message should be
-
src/buildscript_*.sh
:- これらのファイルでは、
reflect
、unicode/utf16
、encoding/json
パッケージのビルド関連のセクションが、ファイルの異なる位置に移動されています。これは、ビルドプロセスの内部的な調整によるもので、機能的な変更ではありません。
- これらのファイルでは、
コアとなるコードの解説
このコミットの核心は、Goのencoding/json
パッケージが、構造体タグの,string
オプションの誤用によって発生するデコードエラーに対して、より具体的で診断に役立つエラーメッセージを提供するようになった点です。
以前は、例えばJSONの"true"
をGoのint
型にデコードしようとした場合など、,string
オプションが意図しない型変換を引き起こした際に、UnmarshalTypeError
やerrPhase
といった一般的なエラーが返されていました。これらのエラーは、問題がどこにあるのかを特定するのに十分な情報を含んでいませんでした。
新しい実装では、literalStore
関数にfromQuoted
というブール値の引数が追加されました。この引数は、現在デコードしようとしているJSONリテラルが、構造体タグの,string
オプションによって文字列として扱われているかどうかを示します。
literalStore
関数内でデコードエラーが発生した場合、fromQuoted
がtrue
であれば、デコーダはエラーが,string
オプションの誤用によるものであると判断し、json: invalid use of ,string struct tag, trying to unmarshal %q into %v
という形式の、より詳細なエラーメッセージを生成します。このメッセージは、どのJSON値が、どのGoの型にデコードされようとして失敗したのかを明示し、エラーの原因が,string
タグの不適切な使用にあることを開発者に直接伝えます。
decode_test.go
のwrongStringTests
の更新は、この新しいエラーメッセージが期待通りに生成されることを保証するためのものです。これにより、開発者は、,string
オプションの誤用に関する問題を迅速に特定し、修正できるようになります。
ビルドスクリプトの変更は、Goのビルドシステムにおけるパッケージの依存関係やビルド順序の内部的な調整を反映しており、encoding/json
パッケージの機能的な改善とは直接関連しません。
関連リンク
- Go Issue 2331: json: confusing error message with unnecessary ,string modifier
- Go Change List 5544045: https://golang.org/cl/5544045
参考にした情報源リンク
- Go
encoding/json
package documentation (Go公式ドキュメント) - Go
encoding/json
struct tags (json:",string"
) に関するWeb記事:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGtuxcHpmoAUcRF6s-tEpo8QklTvfg6RyeEbW92tAbvEpueuQ7bKrREDMs-kBG_T9LVIsXuObgtzUqo6EcPSZRKac4LKlj8CgFnAKUieDYoQaABwIkfdlP6jHM=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFH3cuTRBtpexf1jY0GvzZK9XdX4IDZ7IoJchtHApHMZrOwDkZ5uoWgUMniSVuDXMhX0nxlxwPVqehH8HPzzdqbaN-5FQWQb-HO0qUSzLfCjgbApX8bQvPLftatPH0=
- Go Issue 2331に関するWeb検索結果: