[インデックス 11245] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/json
パッケージにおいて、JSONタグ名にスラッシュ (/
) とパーセント (%
) の使用を許可するように変更を加えるものです。これにより、より柔軟なJSONタグの定義が可能となり、特定のユースケース(例えばMIMEタイプをタグ名に含めるなど)に対応できるようになります。
具体的には、src/pkg/encoding/json/encode.go
内の isValidTag
関数が修正され、タグ名の有効性チェックロジックが更新されました。また、この変更を検証するために src/pkg/encoding/json/tagkey_test.go
に新しいテストケースが追加されています。
コミット
- コミットハッシュ:
b39c883e292a39a0ac05507b5d79d89cc7328836
- 作者: Brad Fitzpatrick bradfitz@golang.org
- コミット日時: 2012年1月18日 水曜日 19:05:15 -0800
- 変更ファイル:
src/pkg/encoding/json/encode.go
: 7行追加、2行削除src/pkg/encoding/json/tagkey_test.go
: 6行追加、1行削除
- 合計変更: 2ファイル変更、13行追加、3行削除
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b39c883e292a39a0ac05507b5d79d89cc7328836
元コミット内容
encoding/json: allow / and % in tag names
Fixes #2718
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5532095
変更の背景
このコミットは、Go言語の encoding/json
パッケージにおける既存の制限、すなわちJSONタグ名に使用できる文字の制約を緩和するために行われました。元々、encoding/json
パッケージは構造体のフィールドをJSONにエンコード(またはデコード)する際に、構造体タグ(json:"tagName"
の形式)を使用してJSONフィールド名を指定します。しかし、このタグ名に使用できる文字には制限があり、スラッシュ (/
) やパーセント (%
) のような一部の記号が許可されていませんでした。
この制限は、特定のユースケースにおいて問題を引き起こしていました。例えば、MIMEタイプ(例: text/html
)やURLエンコードされた文字列(例: foo%20bar
)をJSONタグ名として直接使用したい場合に、既存のバリデーションルールによって拒否されてしまうという課題がありました。
この問題は、GoのIssueトラッカーで「Issue 2718: Valid keys for json.Mashall」として報告されていました。このIssueは、encoding/json
パッケージがより柔軟なタグ名をサポートすべきであるという要望を反映しており、特にWebサービスやAPI連携において、より多様な命名規則に対応する必要性があることを示唆していました。
このコミットは、このIssueを解決し、開発者がより表現力豊かで、特定の標準や慣習に準拠したJSONタグ名を定義できるようにすることを目的としています。これにより、encoding/json
パッケージの汎用性と実用性が向上します。
前提知識の解説
Go言語の encoding/json
パッケージ
Go言語の encoding/json
パッケージは、Goのデータ構造とJSONデータの間でエンコード(GoからJSONへ)およびデコード(JSONからGoへ)を行うための標準ライブラリです。このパッケージは、WebアプリケーションやAPI開発において、JSON形式のデータを扱う際に不可欠な機能を提供します。
主な機能として、json.Marshal
関数と json.Unmarshal
関数があります。
json.Marshal
: Goの構造体、マップ、スライスなどの値をJSON形式のバイトスライスに変換します。json.Unmarshal
: JSON形式のバイトスライスをGoのデータ構造に変換します。
JSON構造体タグ (Struct Tags)
Goの構造体タグは、構造体のフィールドにメタデータを付与するためのメカニズムです。encoding/json
パッケージでは、このタグを使用して、Goのフィールド名とJSONのフィールド名のマッピングをカスタマイズしたり、フィールドのエンコード/デコード動作を制御したりします。
例:
type User struct {
Name string `json:"full_name"` // GoのNameフィールドをJSONではfull_nameとして扱う
Email string `json:"email,omitempty"` // emailフィールドが空の場合、JSONから省略する
Age int `json:"-"` // AgeフィールドをJSONに含めない
}
json:"tagName"
の形式で指定される tagName
が、このコミットで変更の対象となる「タグ名」です。
unicode
パッケージと unicode.IsLetter
, unicode.IsDigit
Go言語の unicode
パッケージは、Unicode文字のプロパティ(文字種、カテゴリなど)を扱うための機能を提供します。このパッケージには、特定の文字がアルファベットであるか、数字であるかなどを判定するための関数が含まれています。
unicode.IsLetter(r rune) bool
: 指定されたルーンr
がUnicodeの文字(アルファベット)である場合にtrue
を返します。unicode.IsDigit(r rune) bool
: 指定されたルーンr
がUnicodeの数字である場合にtrue
を返します。
これらの関数は、文字列のバリデーションやパース処理において、文字の種類を判別するために広く使用されます。このコミットでは、JSONタグ名の有効性をチェックする isValidTag
関数内でこれらの関数が使用されていました。
技術的詳細
このコミットの技術的な核心は、encoding/json
パッケージ内の isValidTag
関数の変更にあります。この関数は、Goの構造体タグとして指定された文字列が、JSONフィールド名として有効な文字のみで構成されているかを検証する役割を担っています。
isValidTag
関数の変更前
変更前の isValidTag
関数は、タグ名の各文字をループでチェックし、以下の条件に合致しない場合に false
を返していました。
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
if c != '$' && c != '-' && c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
return true
}
このロジックでは、タグ名に使用できる文字は、$
、-
、_
、またはUnicodeの文字(アルファベット)、数字に限定されていました。したがって、スラッシュ (/
) やパーセント (%
) は無効な文字として扱われ、これらの文字を含むタグ名は許可されませんでした。
isValidTag
関数の変更後
変更後の isValidTag
関数では、switch
ステートメントが導入され、スラッシュ (/
) とパーセント (%
) が明示的に許可されるようになりました。
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch c {
case '$', '-', '_', '/', '%':
// Acceptable
default:
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
}
return true
}
この変更により、c
が $
、-
、_
、/
、%
のいずれかである場合は Acceptable
と判断され、それ以外の場合にのみ unicode.IsLetter(c)
または unicode.IsDigit(c)
でのチェックが行われるようになりました。これにより、/
と %
がタグ名として有効な文字セットに追加されました。
テストケースの追加
この機能変更の正当性を検証するために、src/pkg/encoding/json/tagkey_test.go
に新しいテストケース percentSlashTag
が追加されました。
type percentSlashTag struct {
V string `json:"text/html%"` // http://golang.org/issue/2718
}
そして、structTagObjectKeyTests
というテストデータスライスに、この新しい構造体を使用したテストエントリが追加されました。
{percentSlashTag{"brut"}, "brut", "text/html%"},
このテストケースは、json:"text/html%"
のようにスラッシュとパーセントを含むタグ名が正しく処理され、期待されるJSONキーとしてエンコードされることを確認します。これにより、isValidTag
関数の変更が意図通りに機能し、後方互換性を損なうことなく新しい文字を許可していることが保証されます。
また、テストの失敗メッセージも改善され、t.Fatalf("Unexpected key: %#q", i)
から t.Fatalf("Unexpected key: %#q, from %#q", i, b)
へと変更されました。これにより、テストが失敗した場合に、どの元のバイト列から予期しないキーが生成されたのかがより明確に表示されるようになり、デバッグが容易になります。
コアとなるコードの変更箇所
src/pkg/encoding/json/encode.go
diff --git a/src/pkg/encoding/json/encode.go b/src/pkg/encoding/json/encode.go
index 727e8174bd..042142d2c5 100644
--- a/src/pkg/encoding/json/encode.go
+++ b/src/pkg/encoding/json/encode.go
@@ -419,8 +419,13 @@ func isValidTag(s string) bool {
\treturn false
}\n \tfor _, c := range s {\n-\t\tif c != '$' && c != '-' && c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {\n-\t\t\treturn false
+\t\tswitch c {\n+\t\tcase '$', '-', '_', '/', '%':\n+\t\t\t// Acceptable\n+\t\tdefault:\n+\t\t\tif !unicode.IsLetter(c) && !unicode.IsDigit(c) {\n+\t\t\t\treturn false
+\t\t\t}\n \t\t}\n \t}\n \treturn true
src/pkg/encoding/json/tagkey_test.go
diff --git a/src/pkg/encoding/json/tagkey_test.go b/src/pkg/encoding/json/tagkey_test.go
index 31fe2be362..1a15241cb0 100644
--- a/src/pkg/encoding/json/tagkey_test.go
+++ b/src/pkg/encoding/json/tagkey_test.go
@@ -36,6 +36,10 @@ type miscPlaneTag struct {\n \tV string `json:\"色は匂へど\"`\n }\n \n+type percentSlashTag struct {\n+\tV string `json:\"text/html%\"` // http://golang.org/issue/2718\n+}\n+\n type emptyTag struct {\n \tW string\n }\n@@ -68,6 +72,7 @@ var structTagObjectKeyTests = []struct {\n \t{misnamedTag{\"Animal Kingdom\"}, \"Animal Kingdom\", \"X\"},\n \t{badFormatTag{\"Orfevre\"}, \"Orfevre\", \"Y\"},\n \t{badCodeTag{\"Reliable Man\"}, \"Reliable Man\", \"Z\"},\n+\t{percentSlashTag{\"brut\"}, \"brut\", \"text/html%\"},\n }\n \n func TestStructTagObjectKey(t *testing.T) {\n@@ -88,7 +93,7 @@ func TestStructTagObjectKey(t *testing.T) {\n \t\t\t\t\tt.Fatalf(\"Unexpected value: %#q, want %v\", s, tt.value)\n \t\t\t\t}\n \t\t\tdefault:\n-\t\t\t\tt.Fatalf(\"Unexpected key: %#q\", i)\n+\t\t\t\tt.Fatalf(\"Unexpected key: %#q, from %#q\", i, b)\n \t\t\t}\n \t\t}\n \t}\n```
## コアとなるコードの解説
### `src/pkg/encoding/json/encode.go` の変更
このファイルでは、`isValidTag` 関数の実装が変更されています。
元のコードでは、タグ名の各文字 `c` が `$`、`-`、`_` のいずれでもなく、かつ `unicode.IsLetter(c)` (文字である) でも `unicode.IsDigit(c)` (数字である) でもない場合に `false` を返していました。これは、許可されていない文字が見つかった時点でタグが無効であると判断するロジックです。
変更後のコードでは、`switch` ステートメントが導入されています。
`case '$', '-', '_', '/', '%':` の行が追加され、`$`、`-`、`_` に加えて、新たに `/` (スラッシュ) と `%` (パーセント) が明示的に「許容される文字」としてリストアップされました。これらの文字が見つかった場合、`// Acceptable` コメントの通り、特に処理は行われず、ループは次の文字へと進みます。
`default:` ブロックは、`switch` の `case` にマッチしなかった文字に対する処理です。このブロック内では、元のコードと同じ `if !unicode.IsLetter(c) && !unicode.IsDigit(c)` のチェックが行われます。これにより、`$`、`-`、`_`、`/`、`%` 以外の文字については、引き続きアルファベットまたは数字であるかどうかが検証されます。
この変更により、JSONタグ名にスラッシュとパーセントを含めることが正式に許可されるようになりました。
### `src/pkg/encoding/json/tagkey_test.go` の変更
このファイルでは、`encoding/json` パッケージのタグ処理に関するテストが拡張されています。
まず、`percentSlashTag` という新しい構造体が定義されました。
```go
type percentSlashTag struct {
V string `json:"text/html%"` // http://golang.org/issue/2718
}
この構造体は、json:"text/html%"
というタグを持つフィールド V
を含んでいます。このタグは、スラッシュ (/
) とパーセント (%
) の両方を含んでおり、まさに今回の変更で許可されるようになった文字を使用しています。コメント // http://golang.org/issue/2718
は、このテストケースが関連するGoのIssue 2718に対応していることを示しています。
次に、structTagObjectKeyTests
という既存のテストデータスライスに、この新しい構造体を使用したテストエントリが追加されました。
{percentSlashTag{"brut"}, "brut", "text/html%"},
このエントリは、percentSlashTag
型の構造体をエンコードした際に、期待されるJSONキーが "text/html%"
であることを検証します。これにより、isValidTag
関数の変更が正しく機能し、スラッシュとパーセントを含むタグ名がJSONキーとして適切に扱われることが保証されます。
最後に、TestStructTagObjectKey
関数内のエラーメッセージが改善されました。
-\t\t\t\tt.Fatalf("Unexpected key: %#q", i)\n+\t\t\t\tt.Fatalf("Unexpected key: %#q, from %#q", i, b)\n
元の t.Fatalf("Unexpected key: %#q", i)
は、予期しないキー i
が見つかった場合にエラーを報告していました。変更後の t.Fatalf("Unexpected key: %#q, from %#q", i, b)
は、予期しないキー i
に加えて、そのキーが生成された元のバイト列 b
も出力するようになりました。これにより、テストが失敗した際に、どの入力データから問題のあるキーが生成されたのかがより明確になり、デバッグの効率が向上します。
これらの変更は、encoding/json
パッケージの堅牢性を高め、より多様なJSONタグ名のユースケースに対応できるようにするための重要なステップです。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/b39c883e292a39a0ac05507b5d79d89cc7328836
- Go Issue 2718: https://github.com/golang/go/issues/2718 (Valid keys for json.Mashall)
- Gerrit Change-ID: https://golang.org/cl/5532095
参考にした情報源リンク
- Go Issue 2718: Valid keys for json.Mashall - https://github.com/golang/go/issues/2718
- Go言語
encoding/json
パッケージ公式ドキュメント: https://pkg.go.dev/encoding/json - Go言語
unicode
パッケージ公式ドキュメント: https://pkg.go.dev/unicode - Go言語の構造体タグに関する解説記事 (一般的な情報源):
- A Tour of Go - Structs: https://go.dev/tour/moretypes/5
- Go by Example: JSON: https://gobyexample.com/json