[インデックス 13792] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/json
パッケージにおける、匿名(埋め込み)フィールドの扱いに関する重要な変更を導入しています。具体的には、JSONエンコーディングおよびデコーディング時に、構造体に埋め込まれた匿名フィールドが適切に処理されるように改善されました。
コミット
commit f97bfb93f45339b1e528bd789495d894a2bfeece
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 10 23:31:40 2012 -0400
encoding/json: handle anonymous fields
Fixes #3069.
R=golang-dev, n13m3y3r
CC=golang-dev
https://golang.org/cl/6460044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f97bfb93f45339b1e528bd789495d894a2bfeece
元コミット内容
encoding/json
パッケージが匿名フィールドを処理するように変更されました。これにより、Issue #3069 が修正されます。
変更の背景
Go言語の encoding/json
パッケージは、Goの構造体とJSONデータの間で変換を行うための重要なツールです。このコミットが導入される以前(Go 1.1より前)は、encoding/json
は構造体内に埋め込まれた匿名フィールドを無視していました。これは、Goの構造体の埋め込み機能が、あたかも埋め込まれた型のフィールドが外側の構造体のフィールドであるかのように振る舞うという言語のセマンティクスと矛盾していました。
具体的には、以下のような問題がありました。
- 匿名フィールドの無視: 構造体に匿名で埋め込まれたフィールドは、JSONへのエンコード時、またはJSONからのデコード時に、そのフィールドが持つ値がJSON出力に含まれない、あるいはJSON入力からそのフィールドに値が設定されないという問題がありました。これにより、開発者はGoの埋め込み機能の恩恵をJSONシリアライズ/デシリアライズの文脈で十分に享受できませんでした。
- Issue #3069: この問題は、GoのIssueトラッカーで「Issue #3069: encoding/json: handle anonymous fields」として報告されていました。このIssueは、
encoding/xml
パッケージが埋め込みフィールドを処理する方法と同様に、encoding/json
も匿名フィールドをフラット化して扱うべきであるという議論を促しました。
このコミットは、これらの問題を解決し、encoding/json
パッケージがGoの構造体の埋め込みセマンティクスにより忠実になるように設計されました。
前提知識の解説
Go言語の構造体と匿名フィールド
Go言語では、構造体の中に他の構造体やインターフェースをフィールド名なしで宣言することができます。これを「匿名フィールド」または「埋め込みフィールド」と呼びます。匿名フィールドは、その型が持つメソッドやフィールドを、外側の構造体が直接持っているかのようにアクセスできるようにする、一種の継承のような機能を提供します。
例:
type Person struct {
Name string
Age int
}
type Employee struct {
Person // 匿名フィールド
ID string
}
func main() {
e := Employee{
Person: Person{Name: "Alice", Age: 30},
ID: "E123",
}
fmt.Println(e.Name) // Alice (PersonのNameフィールドに直接アクセスできる)
}
encoding/json
パッケージの基本的な動作
encoding/json
パッケージは、Goの構造体のフィールド名とJSONのキーをマッピングして、Goの値をJSON形式に変換(マーシャリング)したり、JSONデータをGoの値に変換(アンマーシャリング)したりします。フィールド名とJSONキーのマッピングは、構造体タグ(json:"key_name"
)を使ってカスタマイズできます。
例:
type User struct {
Name string `json:"user_name"`
Email string `json:"email_address"`
}
// User{Name: "Bob", Email: "bob@example.com"} は
// {"user_name": "Bob", "email_address": "bob@example.com"} にマーシャリングされる
変更前の encoding/json
の匿名フィールドの扱い
このコミット以前は、encoding/json
は匿名フィールドを特別扱いせず、単に無視していました。つまり、上記の Employee
構造体をJSONにマーシャリングしようとすると、Person
構造体のフィールド(Name
, Age
)はJSON出力に含まれませんでした。これは、Goの言語仕様における匿名フィールドの振る舞いと乖離していました。
技術的詳細
このコミットの主要な変更点は、encoding/json
パッケージが構造体のフィールドを走査し、JSONエンコーディング/デコーディングの対象となるフィールドを決定するロジックを根本的に見直したことです。特に、匿名フィールドの処理が大幅に改善されました。
新しい動作は以下の原則に基づいています。
- 匿名フィールドのフラット化: 匿名で埋め込まれた構造体のエクスポートされたフィールドは、あたかもそれらが外側の構造体の直接のフィールドであるかのように、JSON出力にフラット化されます。これにより、Goの埋め込みセマンティクスがJSONシリアライズにも適用されるようになります。
- 名前の衝突解決: 複数の匿名フィールドや、匿名フィールドと外側の構造体のフィールドで同じJSONキー名(またはGoのフィールド名)が衝突した場合、
encoding/json
は特定の解決ルールに従います。- 優先順位: 一般的に、外側の構造体に近いフィールド、または構造体定義で後に出現するフィールドが優先されます。
- タグの優先: 明示的な
json
タグを持つフィールドは、タグを持たないフィールドよりも優先されます。 - 「アニヒレーション(Annihilation)」: 複数の匿名フィールドが同じ名前のフィールドを持つ場合、それらのフィールドは「アニヒレーション」され、JSONエンコーディング/デコーディングの対象から除外されることがあります。これは、曖昧さを避けるためのメカニズムです。
json:"-"
タグのサポート: フィールド(匿名フィールドを含む)にjson:"-"
タグが指定されている場合、そのフィールドはJSONのエンコーディングおよびデコーディングから完全に無視されます。これは、特定のフィールドを意図的にJSONから除外したい場合に有用です。omitempty
オプションの適用:json:",omitempty"
タグは、フィールドがその型のゼロ値(例: 数値の0、空文字列、nilポインタ、空のスライス/マップ)である場合に、JSON出力からそのフィールドを省略するために使用されます。この動作は匿名フィールドのフラット化されたフィールドにも適用されます。
これらの変更は、主に src/pkg/encoding/json/encode.go
と src/pkg/encoding/json/decode.go
内のフィールド探索ロジック、特に typeFields
関数(またはそれに相当するロジック)に影響を与えています。この関数は、構造体の型を受け取り、JSONのエンコーディング/デコーディングに関連するすべてのフィールド(匿名フィールドから昇格されたフィールドを含む)のリストを生成します。このリストは、フィールド名、JSONタグ、インデックスパス(ネストされたフィールドへのアクセスパス)、およびその他のオプション(omitempty
、string
)を含みます。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
src/pkg/encoding/json/decode.go
: JSONデコード時の匿名フィールドの処理ロジックが変更されました。特に、構造体のフィールドを検索する部分が、新しいcachedTypeFields
関数を使用するように書き換えられています。これにより、匿名フィールドが適切に解決され、対応するJSONキーがGoの構造体フィールドにマッピングされるようになります。- 変更前は、
sv.NumField()
をループして手動でフィールドを検索し、タグや名前の一致を調べていました。匿名フィールドはcontinue
でスキップされていました。 - 変更後は、
cachedTypeFields(sv.Type())
を呼び出して、事前に計算されたフィールドのリスト (field
構造体のスライス) を取得し、そのリストからキーに一致するフィールドを探すようになりました。これにより、匿名フィールドの解決ロジックがtypeFields
関数に集約されました。
- 変更前は、
-
src/pkg/encoding/json/decode_test.go
: 匿名フィールドのデコードに関する新しいテストケースが多数追加されました。これにより、新しい動作が期待通りであることを検証しています。Point
,Top
,Embed0
,Embed0a
,Embed0b
,Embed0c
,Embed0p
,Embed0q
,Loop
,S5
,S6
,S7
,S8
,S9
,S10
,S11
,S12
,S13
,Ambig
といった、匿名フィールドや名前の衝突、タグの優先順位などを検証するための複雑な構造体が定義されています。- これらの構造体を用いた
unmarshalTest
のエントリが追加され、様々なシナリオでのJSONデコードの挙動がテストされています。 - 既存の
TestAnonymous
関数は削除されました。これは、匿名フィールドが無視されることをテストしていたものであり、新しい動作では無視されないため不要になったためです。
-
src/pkg/encoding/json/encode.go
: JSONエンコード時の匿名フィールドの処理ロジックが変更されました。特に、構造体のフィールドを走査してエンコードする部分が、新しいcachedTypeFields
関数を使用するように書き換えられています。- 変更前は
encodeFields(v.Type())
を使用していましたが、変更後はcachedTypeFields(v.Type())
を使用しています。 encodeField
構造体とencodeFields
関数が削除され、代わりにfield
構造体とtypeFields
/cachedTypeFields
関数が導入されました。typeFields
関数は、構造体の型を受け取り、JSONエンコーディングの対象となるすべてのフィールド(匿名フィールドから昇格されたフィールドを含む)のリストを生成します。この関数は、幅優先探索(BFS)アルゴリズムを使用して、埋め込み構造体を再帰的に探索し、フィールドの衝突解決(名前、タグ、深さに基づく)を行います。fieldByIndex
ヘルパー関数が追加され、reflect.Value
とインデックスパス([]int
)からネストされたフィールドのreflect.Value
を取得できるようになりました。byName
とbyIndex
というソートヘルパーが追加され、フィールドのリストを名前やインデックス順にソートするために使用されます。これは、衝突解決と決定論的なフィールド順序を保証するために重要です。fieldCache
というグローバルキャッシュが導入され、cachedTypeFields
が計算結果をキャッシュすることで、同じ型に対するフィールド探索の繰り返しを避けるように最適化されました。
- 変更前は
コアとなるコードの解説
このコミットの核心は、src/pkg/encoding/json/encode.go
に新しく導入された typeFields
関数とその関連ヘルパーです。この関数は、Goの構造体からJSONエンコーディング/デコーディングの対象となるフィールドのリストを構築する複雑なロジックをカプセル化しています。
typeFields
関数の役割
typeFields(t reflect.Type) []field
関数は、与えられた reflect.Type
(通常は構造体の型)に対して、JSONのマーシャリングおよびアンマーシャリングで考慮すべきすべてのフィールドを field
構造体のスライスとして返します。この関数は、以下の重要な処理を行います。
- 幅優先探索 (BFS): 構造体とその中に埋め込まれた匿名フィールドを幅優先で探索します。これにより、外側の構造体に近いフィールドが優先されるというGoの埋め込みセマンティクスが反映されます。
- フィールドの収集: 各フィールドについて、その名前、JSONタグの有無、インデックスパス(
[]int
で表現される、reflect.Value.FieldByIndex
で使用されるパス)、元のreflect.Type
、omitempty
オプション、string
オプションなどの情報をfield
構造体に格納します。 - 匿名フィールドの昇格: 匿名フィールドの場合、そのエクスポートされたフィールドが外側の構造体のフィールドとして「昇格」されます。ただし、
json:"-"
タグが指定されている場合は無視されます。 - 名前の衝突解決: 複数のフィールドが同じJSONキー名に解決される場合(特に匿名フィールドが原因で発生しやすい)、
typeFields
は以下のルールで衝突を解決します。byName
ソート: まず、フィールドを名前でソートし、次に深さ(インデックスパスの長さ)、そしてJSONタグの有無でソートします。これにより、同じ名前のフィールドが隣接するように配置されます。- アニヒレーション: ソート後、同じ名前を持つフィールドが複数存在し、かつそれらのフィールドが明示的なJSONタグを持たない場合(またはタグの優先順位が低い場合)、それらのフィールドは「アニヒレーション」され、結果のリストから削除されます。これは、曖昧なフィールドをJSON処理から除外することで、予期せぬ動作を防ぐためのものです。
byIndex
ソート: 最終的に、衝突解決されたフィールドのリストは、元の構造体での宣言順(インデックスパス順)にソートされます。これにより、JSON出力のフィールド順序が決定論的になります。
field
構造体
新しく導入された field
構造体は、JSONエンコーディング/デコーディングに関連する各フィールドのメタデータを保持します。
type field struct {
name string // JSONキー名
tag bool // JSONタグが明示的に指定されたか
index []int // reflect.Value.FieldByIndexで使用するインデックスパス
typ reflect.Type // フィールドの型
omitEmpty bool // omitemptyオプションが指定されたか
quoted bool // ,stringオプションが指定されたか
}
cachedTypeFields
関数
cachedTypeFields(t reflect.Type) []field
は、typeFields
のラッパー関数であり、計算されたフィールドのリストをグローバルキャッシュ (fieldCache
) に保存し、再利用することでパフォーマンスを向上させます。これにより、同じ構造体型に対して何度もフィールド探索を行う必要がなくなります。
これらの変更により、encoding/json
パッケージはGoの匿名フィールドをより自然に、かつ予測可能な方法で処理できるようになり、Goの構造体とJSON間のマッピングがより強力で柔軟になりました。
関連リンク
- Go CL: https://golang.org/cl/6460044
- Go Issue #3069: https://github.com/golang/go/issues/3069 (このコミットによって修正されたIssue)
参考にした情報源リンク
- Go Blog: JSON and Go: https://go.dev/blog/json
- GoDoc
encoding/json
package: https://pkg.go.dev/encoding/json - Stack Overflow: How does Go's json.Marshal handle embedded structs?: https://stackoverflow.com/questions/17700062/how-does-gos-json-marshal-handle-embedded-structs
- Go
encoding/json/v2
(inline option): https://pkg.go.dev/encoding/json/v2 (このコミットの直接の関連ではないが、匿名フィールドの進化を示す参考情報) - Google Search for "golang encoding/json issue 3069 anonymous fields"I have generated the comprehensive technical explanation in Markdown format, following all your instructions and the specified chapter structure. The output is ready to be printed to standard output.
# [インデックス 13792] ファイルの概要
このコミットは、Go言語の標準ライブラリである `encoding/json` パッケージにおける、匿名(埋め込み)フィールドの扱いに関する重要な変更を導入しています。具体的には、JSONエンコーディングおよびデコーディング時に、構造体に埋め込まれた匿名フィールドが適切に処理されるように改善されました。
## コミット
commit f97bfb93f45339b1e528bd789495d894a2bfeece Author: Russ Cox rsc@golang.org Date: Mon Sep 10 23:31:40 2012 -0400
encoding/json: handle anonymous fields
Fixes #3069.
R=golang-dev, n13m3y3r
CC=golang-dev
https://golang.org/cl/6460044
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/f97bfb93f45339b1e528bd789495d894a2bfeece](https://github.com/golang/go/commit/f97bfb93f45339b1e528bd789495d894a2bfeece)
## 元コミット内容
`encoding/json` パッケージが匿名フィールドを処理するように変更されました。これにより、Issue #3069 が修正されます。
## 変更の背景
Go言語の `encoding/json` パッケージは、Goの構造体とJSONデータの間で変換を行うための重要なツールです。このコミットが導入される以前(Go 1.1より前)は、`encoding/json` は構造体内に埋め込まれた匿名フィールドを無視していました。これは、Goの構造体の埋め込み機能が、あたかも埋め込まれた型のフィールドが外側の構造体のフィールドであるかのように振る舞うという言語のセマンティクスと矛盾していました。
具体的には、以下のような問題がありました。
1. **匿名フィールドの無視**: 構造体に匿名で埋め込まれたフィールドは、JSONへのエンコード時、またはJSONからのデコード時に、そのフィールドが持つ値がJSON出力に含まれない、あるいはJSON入力からそのフィールドに値が設定されないという問題がありました。これにより、開発者はGoの埋め込み機能の恩恵をJSONシリアライズ/デシリアライズの文脈で十分に享受できませんでした。
2. **Issue #3069**: この問題は、GoのIssueトラッカーで「Issue #3069: encoding/json: handle anonymous fields」として報告されていました。このIssueは、`encoding/xml` パッケージが埋め込みフィールドを処理する方法と同様に、`encoding/json` も匿名フィールドをフラット化して扱うべきであるという議論を促しました。
このコミットは、これらの問題を解決し、`encoding/json` パッケージがGoの構造体の埋め込みセマンティクスにより忠実になるように設計されました。
## 前提知識の解説
### Go言語の構造体と匿名フィールド
Go言語では、構造体の中に他の構造体やインターフェースをフィールド名なしで宣言することができます。これを「匿名フィールド」または「埋め込みフィールド」と呼びます。匿名フィールドは、その型が持つメソッドやフィールドを、外側の構造体が直接持っているかのようにアクセスできるようにする、一種の継承のような機能を提供します。
例:
```go
type Person struct {
Name string
Age int
}
type Employee struct {
Person // 匿名フィールド
ID string
}
func main() {
e := Employee{
Person: Person{Name: "Alice", Age: 30},
ID: "E123",
}
fmt.Println(e.Name) // Alice (PersonのNameフィールドに直接アクセスできる)
}
encoding/json
パッケージの基本的な動作
encoding/json
パッケージは、Goの構造体のフィールド名とJSONのキーをマッピングして、Goの値をJSON形式に変換(マーシャリング)したり、JSONデータをGoの値に変換(アンマーシャリング)したりします。フィールド名とJSONキーのマッピングは、構造体タグ(json:"key_name"
)を使ってカスタマイズできます。
例:
type User struct {
Name string `json:"user_name"`
Email string `json:"email_address"`
}
// User{Name: "Bob", Email: "bob@example.com"} は
// {"user_name": "Bob", "email_address": "bob@example.com"} にマーシャリングされる
変更前の encoding/json
の匿名フィールドの扱い
このコミット以前は、encoding/json
は匿名フィールドを特別扱いせず、単に無視していました。つまり、上記の Employee
構造体をJSONにマーシャリングしようとすると、Person
構造体のフィールド(Name
, Age
)はJSON出力に含まれませんでした。これは、Goの言語仕様における匿名フィールドの振る舞いと乖離していました。
技術的詳細
このコミットの主要な変更点は、encoding/json
パッケージが構造体のフィールドを走査し、JSONエンコーディング/デコーディングの対象となるフィールドを決定するロジックを根本的に見直したことです。特に、匿名フィールドの処理が大幅に改善されました。
新しい動作は以下の原則に基づいています。
- 匿名フィールドのフラット化: 匿名で埋め込まれた構造体のエクスポートされたフィールドは、あたかもそれらが外側の構造体の直接のフィールドであるかのように、JSON出力にフラット化されます。これにより、Goの埋め込みセマンティクスがJSONシリアライズにも適用されるようになります。
- 名前の衝突解決: 複数の匿名フィールドや、匿名フィールドと外側の構造体のフィールドで同じJSONキー名(またはGoのフィールド名)が衝突した場合、
encoding/json
は特定の解決ルールに従います。- 優先順位: 一般的に、外側の構造体に近いフィールド、または構造体定義で後に出現するフィールドが優先されます。
- タグの優先: 明示的な
json
タグを持つフィールドは、タグを持たないフィールドよりも優先されます。 - 「アニヒレーション(Annihilation)」: 複数の匿名フィールドが同じ名前のフィールドを持つ場合、それらのフィールドは「アニヒレーション」され、JSONエンコーディング/デコーディングの対象から除外されることがあります。これは、曖昧さを避けるためのメカニズムです。
json:"-"
タグのサポート: フィールド(匿名フィールドを含む)にjson:"-"
タグが指定されている場合、そのフィールドはJSONのエンコーディングおよびデコーディングから完全に無視されます。これは、特定のフィールドを意図的にJSONから除外したい場合に有用です。omitempty
オプションの適用:json:",omitempty"
タグは、フィールドがその型のゼロ値(例: 数値の0、空文字列、nilポインタ、空のスライス/マップ)である場合に、JSON出力からそのフィールドを省略するために使用されます。この動作は匿名フィールドのフラット化されたフィールドにも適用されます。
これらの変更は、主に src/pkg/encoding/json/encode.go
と src/pkg/encoding/json/decode.go
内のフィールド探索ロジック、特に typeFields
関数(またはそれに相当するロジック)に影響を与えています。この関数は、構造体の型を受け取り、JSONのエンコーディング/デコーディングに関連するすべてのフィールド(匿名フィールドから昇格されたフィールドを含む)のリストを生成します。このリストは、フィールド名、JSONタグ、インデックスパス(ネストされたフィールドへのアクセスパス)、およびその他のオプション(omitempty
、string
)を含みます。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
src/pkg/encoding/json/decode.go
: JSONデコード時の匿名フィールドの処理ロジックが変更されました。特に、構造体のフィールドを検索する部分が、新しいcachedTypeFields
関数を使用するように書き換えられています。これにより、匿名フィールドが適切に解決され、対応するJSONキーがGoの構造体フィールドにマッピングされるようになります。- 変更前は、
sv.NumField()
をループして手動でフィールドを検索し、タグや名前の一致を調べていました。匿名フィールドはcontinue
でスキップされていました。 - 変更後は、
cachedTypeFields(sv.Type())
を呼び出して、事前に計算されたフィールドのリスト (field
構造体のスライス) を取得し、そのリストからキーに一致するフィールドを探すようになりました。これにより、匿名フィールドの解決ロジックがtypeFields
関数に集約されました。
- 変更前は、
-
src/pkg/encoding/json/decode_test.go
: 匿名フィールドのデコードに関する新しいテストケースが多数追加されました。これにより、新しい動作が期待通りであることを検証しています。Point
,Top
,Embed0
,Embed0a
,Embed0b
,Embed0c
,Embed0p
,Embed0q
,Loop
,S5
,S6
,S7
,S8
,S9
,S10
,S11
,S12
,S13
,Ambig
といった、匿名フィールドや名前の衝突、タグの優先順位などを検証するための複雑な構造体が定義されています。- これらの構造体を用いた
unmarshalTest
のエントリが追加され、様々なシナリオでのJSONデコードの挙動がテストされています。 - 既存の
TestAnonymous
関数は削除されました。これは、匿名フィールドが無視されることをテストしていたものであり、新しい動作では無視されないため不要になったためです。
-
src/pkg/encoding/json/encode.go
: JSONエンコード時の匿名フィールドの処理ロジックが変更されました。特に、構造体のフィールドを走査してエンコードする部分が、新しいcachedTypeFields
関数を使用するように書き換えられています。- 変更前は
encodeFields(v.Type())
を使用していましたが、変更後はcachedTypeFields(v.Type())
を使用しています。 encodeField
構造体とencodeFields
関数が削除され、代わりにfield
構造体とtypeFields
/cachedTypeFields
関数が導入されました。typeFields
関数は、構造体の型を受け取り、JSONエンコーディングの対象となるすべてのフィールド(匿名フィールドから昇格されたフィールドを含む)のリストを生成します。この関数は、幅優先探索(BFS)アルゴリズムを使用して、埋め込み構造体を再帰的に探索し、フィールドの衝突解決(名前、タグ、深さに基づく)を行います。fieldByIndex
ヘルパー関数が追加され、reflect.Value
とインデックスパス([]int
)からネストされたフィールドのreflect.Value
を取得できるようになりました。byName
とbyIndex
というソートヘルパーが追加され、フィールドのリストを名前やインデックス順にソートするために使用されます。これは、衝突解決と決定論的なフィールド順序を保証するために重要です。fieldCache
というグローバルキャッシュが導入され、cachedTypeFields
が計算結果をキャッシュすることで、同じ型に対するフィールド探索の繰り返しを避けるように最適化されました。
- 変更前は
コアとなるコードの解説
このコミットの核心は、src/pkg/encoding/json/encode.go
に新しく導入された typeFields
関数とその関連ヘルパーです。この関数は、Goの構造体からJSONエンコーディング/デコーディングの対象となるフィールドのリストを構築する複雑なロジックをカプセル化しています。
typeFields
関数の役割
typeFields(t reflect.Type) []field
関数は、与えられた reflect.Type
(通常は構造体の型)に対して、JSONのマーシャリングおよびアンマーシャリングで考慮すべきすべてのフィールドを field
構造体のスライスとして返します。この関数は、以下の重要な処理を行います。
- 幅優先探索 (BFS): 構造体とその中に埋め込まれた匿名フィールドを幅優先で探索します。これにより、外側の構造体に近いフィールドが優先されるというGoの埋め込みセマンティクスが反映されます。
- フィールドの収集: 各フィールドについて、その名前、JSONタグの有無、インデックスパス(
[]int
で表現される、reflect.Value.FieldByIndex
で使用されるパス)、元のreflect.Type
、omitempty
オプション、string
オプションなどの情報をfield
構造体に格納します。 - 匿名フィールドの昇格: 匿名フィールドの場合、そのエクスポートされたフィールドが外側の構造体のフィールドとして「昇格」されます。ただし、
json:"-"
タグが指定されている場合は無視されます。 - 名前の衝突解決: 複数のフィールドが同じJSONキー名に解決される場合(特に匿名フィールドが原因で発生しやすい)、
typeFields
は以下のルールで衝突を解決します。byName
ソート: まず、フィールドを名前でソートし、次に深さ(インデックスパスの長さ)、そしてJSONタグの有無でソートします。これにより、同じ名前のフィールドが隣接するように配置されます。- アニヒレーション: ソート後、同じ名前を持つフィールドが複数存在し、かつそれらのフィールドが明示的なJSONタグを持たない場合(またはタグの優先順位が低い場合)、それらのフィールドは「アニヒレーション」され、結果のリストから削除されます。これは、曖昧なフィールドをJSON処理から除外することで、予期せぬ動作を防ぐためのものです。
byIndex
ソート: 最終的に、衝突解決されたフィールドのリストは、元の構造体での宣言順(インデックスパス順)にソートされます。これにより、JSON出力のフィールド順序が決定論的になります。
field
構造体
新しく導入された field
構造体は、JSONエンコーディング/デコーディングに関連する各フィールドのメタデータを保持します。
type field struct {
name string // JSONキー名
tag bool // JSONタグが明示的に指定されたか
index []int // reflect.Value.FieldByIndexで使用するインデックスパス
typ reflect.Type // フィールドの型
omitEmpty bool // omitemptyオプションが指定されたか
quoted bool // ,stringオプションが指定されたか
}
cachedTypeFields
関数
cachedTypeFields(t reflect.Type) []field
は、typeFields
のラッパー関数であり、計算されたフィールドのリストをグローバルキャッシュ (fieldCache
) に保存し、再利用することでパフォーマンスを向上させます。これにより、同じ構造体型に対して何度もフィールド探索を行う必要がなくなります。
これらの変更により、encoding/json
パッケージはGoの匿名フィールドをより自然に、かつ予測可能な方法で処理できるようになり、Goの構造体とJSON間のマッピングがより強力で柔軟になりました。
関連リンク
- Go CL: https://golang.org/cl/6460044
- Go Issue #3069: https://github.com/golang/go/issues/3069 (このコミットによって修正されたIssue)
参考にした情報源リンク
- Go Blog: JSON and Go: https://go.dev/blog/json
- GoDoc
encoding/json
package: https://pkg.go.dev/encoding/json - Stack Overflow: How does Go's json.Marshal handle embedded structs?: https://stackoverflow.com/questions/17700062/how-does-gos-json-marshal-handle-embedded-structs
- Go
encoding/json/v2
(inline option): https://pkg.go.dev/encoding/json/v2 (このコミットの直接の関連ではないが、匿名フィールドの進化を示す参考情報) - Google Search for "golang encoding/json issue 3069 anonymous fields"