[インデックス 14714] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージにおいて、XMLマーシャリング(Goの構造体をXMLに変換する処理)の際に、構造体フィールドに付与された ",any"
タグが正しく処理されるように修正するものです。これまでは ",any"
タグが付いたフィールドは xml.Marshal
関数によって無視されていましたが、この変更により、xml.Unmarshal
関数(XMLをGoの構造体に変換する処理)との対称性が向上し、",any"
フィールドが適切にXML出力に含まれるようになります。
コミット
commit a9121a19f0a2904e1b7a62d4bb07eb82093bb93a
Author: Chris Jones <chris@cjones.org>
Date: Sat Dec 22 10:00:36 2012 -0500
encoding/xml: Marshal ",any" fields
Fixes #3559.
This makes Marshal handle fields marked ",any" instead of ignoring
them. That makes Marshal more symmetrical with Unmarshal, which seems
to have been a design goal.
Note some test cases were changed, because this patch changes
marshalling behavior. I think the previous behavior was buggy, but
there's still a backward-compatibility question to consider.
R=rsc
CC=golang-dev, n13m3y3r
https://golang.org/cl/6938068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a9121a19f0a2904e1b7a62d4bb07eb82093bb93a
元コミット内容
encoding/xml
: ,any
フィールドをマーシャルする
これは、Marshal
が ,any
とマークされたフィールドを無視する代わりに処理するようにします。これにより、Marshal
は Unmarshal
とより対称的になり、それが設計目標であったようです。
このパッチはマーシャリングの動作を変更するため、いくつかのテストケースが変更されました。以前の動作はバグがあったと考えていますが、後方互換性の問題も考慮する必要があります。
変更の背景
このコミットは、Go言語の encoding/xml
パッケージにおける既存の不整合を修正するために導入されました。具体的には、Goの構造体をXMLに変換する xml.Marshal
関数が、構造体フィールドに付与された xml:",any"
タグを適切に処理せず、そのフィールドをXML出力から無視してしまうという問題がありました。
一方で、XMLをGoの構造体に変換する xml.Unmarshal
関数は、xml:",any"
タグが付与されたフィールドを正しく解釈し、未知のXML要素や任意のXMLコンテンツをそのフィールドに格納することができました。この非対称性は、開発者がXMLのラウンドトリップ(マーシャルしてアンマーシャル、またはその逆)を行う際に予期せぬ動作を引き起こす可能性がありました。
コミットメッセージにある Fixes #3559
は、この問題がGoのIssueトラッカーで報告されていたことを示しています。この変更の主な目的は、Marshal
と Unmarshal
の動作を対称的にし、",any"
タグの意図されたセマンティクスを両方向で尊重することでした。これにより、encoding/xml
パッケージの予測可能性と堅牢性が向上します。
前提知識の解説
Go言語の encoding/xml
パッケージ
encoding/xml
パッケージは、Goの構造体とXMLドキュメントの間でデータを変換するための機能を提供します。主な関数は以下の通りです。
xml.Marshal(v interface{}) ([]byte, error)
: Goの任意の値をXML形式のバイトスライスに変換します。構造体のフィールドタグ(例:xml:"name,attr"
,xml:",innerxml"
,xml:",chardata"
,xml:",omitempty"
など)を解釈して、XML要素名、属性、コンテンツなどを決定します。xml.Unmarshal(data []byte, v interface{}) error
: XML形式のバイトスライスをGoの任意の構造体に変換します。
XMLフィールドタグ xml:",any"
encoding/xml
パッケージでは、構造体のフィールドにタグを付与することで、XMLとのマッピング方法を制御できます。xml:",any"
タグは特殊なタグで、主に xml.Unmarshal
のコンテキストで使用されていました。
xml.Unmarshal
における",any"
: このタグが付与されたフィールドは、XMLドキュメント内でそのフィールドに対応する要素が見つからなかった場合、またはその要素が予期しない構造を持っていた場合に、その未知のXMLコンテンツ(要素、属性、テキストなど)を格納するために使用されます。これにより、XMLスキーマに厳密に準拠しないXMLや、拡張可能なXMLを柔軟に処理できるようになります。通常、interface{}
型や[]byte
型、または特定の構造体型にマッピングされます。
マーシャリングとアンマーシャリングの対称性
理想的には、Marshal(Unmarshal(data))
が元の data
に近い結果を生成し、Unmarshal(Marshal(v))
が元の v
に近い結果を生成することが望ましいです。これを「ラウンドトリップ」の対称性と呼びます。",any"
フィールドが Unmarshal
では処理されるが Marshal
では無視されるという非対称性は、このラウンドトリップの期待を裏切るものでした。
技術的詳細
このコミットは、encoding/xml
パッケージ内の4つのファイルに影響を与えています。
-
src/pkg/encoding/xml/marshal.go
:marshalStruct
関数は、Goの構造体をXMLにマーシャルする主要なロジックを含んでいます。- 変更前は、
finfo.flags&(fAttr|fAny) != 0
という条件で、属性 (fAttr
) または",any"
(fAny
) のフラグを持つフィールドがスキップされていました。これは、",any"
フィールドがマーシャリング時に無視される原因でした。 - 変更後、この条件は
finfo.flags&(fAttr) != 0
となり、fAny
フラグを持つフィールドはスキップされなくなりました。 - さらに、
case fElement:
の部分がcase fElement, fElement | fAny:
に変更され、fAny
フラグがfElement
フラグと組み合わされた場合(つまり、",any"
フィールドが要素として扱われる場合)も適切に処理されるようになりました。これにより、",any"
フィールドがXML要素として出力される道が開かれました。
-
src/pkg/encoding/xml/marshal_test.go
:- このファイルには、
xml.Marshal
の動作を検証するためのテストケースが含まれています。 AnyTest
構造体(xml:",any"
フィールドを持つ)のテストケースが大幅に修正されました。- 変更前は、
AnyTest
のマーシャリング結果が<a><nested><value>known</value></nested></a>
となっており、AnyField
の内容が完全に無視されていました。また、UnmarshalOnly: true
やMarshalOnly: true
といったフラグが設定されているテストケースもあり、特定の方向でのみテストされていたことが伺えます。 - 変更後、
AnyTest
のExpectXML
が<a><nested><value>known</value></nested><AnyField><unknown/></AnyField></a>
となり、AnyField
の内容がXML出力に含まれるようになりました。 AnyOmitTest
とAnySliceTest
という新しいテスト構造体が追加され、",any,omitempty"
や",any"
とスライス型の組み合わせが正しくマーシャルされることを検証しています。- 既存のテストケースも、
",any"
フィールドがマーシャルされるようになった新しい動作に合わせてExpectXML
が更新されました。
- このファイルには、
-
src/pkg/encoding/xml/read.go
:unmarshal
関数は、XMLをGoの構造体にアンマーシャルする主要なロジックを含んでいます。case fAny:
の部分がcase fAny, fAny | fElement:
に変更されました。これは、Unmarshal
が",any"
フィールドを処理する際に、それが単なるfAny
フラグを持つ場合でも、fElement
フラグと組み合わされた場合でも、同様にsaveAny
変数に値を格納するように調整されたことを意味します。この変更は、主にmarshal.go
での変更との整合性を保つためのものです。
-
src/pkg/encoding/xml/typeinfo.go
:structFieldInfo
関数は、構造体のフィールド情報(タグ、型など)を解析し、それに対応するfieldInfo
構造体を生成します。if finfo.flags&fMode == fAny { finfo.flags |= fElement }
という行が追加されました。これは、フィールドが",any"
タグを持つ場合 (fAny
フラグが設定されている場合)、自動的にfElement
フラグも設定されるようにします。これにより、",any"
フィールドがデフォルトでXML要素として扱われるようになり、マーシャリング時の動作がより予測可能になります。
これらの変更により、encoding/xml
パッケージは ",any"
タグを持つフィールドを Marshal
時にも適切に処理し、Unmarshal
との対称性を大幅に改善しました。
コアとなるコードの変更箇所
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -273,7 +273,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
s := parentStack{printer: p}
for i := range tinfo.fields {
finfo := &tinfo.fields[i]
- if finfo.flags&(fAttr|fAny) != 0 {
+ if finfo.flags&(fAttr) != 0 {
continue
}
vf := finfo.value(val)
@@ -340,7 +340,7 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
continue
}
- case fElement:
+ case fElement, fElement | fAny:
s.trim(finfo.parents)
if vf.Kind() != reflect.Ptr && vf.Kind() != reflect.Interface || !vf.IsNil() {
src/pkg/encoding/xml/typeinfo.go
--- a/src/pkg/encoding/xml/typeinfo.go
+++ b/src/pkg/encoding/xml/typeinfo.go
@@ -154,6 +154,9 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
// This will also catch multiple modes in a single field.
valid = false
}
+ if finfo.flags&fMode == fAny {
+ finfo.flags |= fElement
+ }
if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
valid = false
}
コアとなるコードの解説
src/pkg/encoding/xml/marshal.go
の変更
-
if finfo.flags&(fAttr|fAny) != 0 { continue }
からif finfo.flags&(fAttr) != 0 { continue }
への変更:- これは
xml.Marshal
が構造体のフィールドを処理するループの初期段階で行われるチェックです。 - 変更前は、フィールドがXML属性 (
fAttr
) であるか、または",any"
(fAny
) タグを持つ場合に、そのフィールドの処理をスキップしていました。これにより、",any"
フィールドはXML出力に現れませんでした。 - 変更後、
fAny
フラグを持つフィールドはスキップされなくなりました。これにより、",any"
フィールドも通常の要素としてマーシャリングの対象となるようになります。
- これは
-
case fElement:
からcase fElement, fElement | fAny:
への変更:- これは、フィールドのタイプ(要素、属性、文字データなど)に基づいて異なるマーシャリングロジックを適用する
switch
ステートメントの一部です。 - 変更前は、フィールドが純粋なXML要素 (
fElement
) としてマークされている場合にのみ、要素のマーシャリングロジックが実行されていました。 - 変更後、フィールドがXML要素 (
fElement
) であるか、またはfElement
とfAny
の両方のフラグを持つ場合(つまり、",any"
フィールドが要素として扱われる場合)にも、同じ要素マーシャリングロジックが適用されるようになりました。これは、",any"
フィールドがXML要素として出力されることを保証します。
- これは、フィールドのタイプ(要素、属性、文字データなど)に基づいて異なるマーシャリングロジックを適用する
src/pkg/encoding/xml/typeinfo.go
の変更
if finfo.flags&fMode == fAny { finfo.flags |= fElement }
の追加:- このコードは、構造体のフィールド情報を解析する際に実行されます。
- フィールドが
",any"
タグを持つ場合(fAny
フラグが設定されている場合)、そのフィールドのフラグにfElement
を追加します。 - これにより、
",any"
フィールドは、明示的に要素として指定されていなくても、デフォルトでXML要素として扱われるようになります。これは、marshal.go
での変更と連携し、",any"
フィールドがXML出力に適切に現れるようにするための重要なステップです。
これらの変更の組み合わせにより、encoding/xml.Marshal
は ",any"
タグが付与されたフィールドを認識し、それらをXML要素として出力するようになります。これにより、Marshal
と Unmarshal
の間の対称性が確立され、",any"
タグの意図された機能が両方向でサポートされることになります。
関連リンク
- Go CL 6938068: https://golang.org/cl/6938068
参考にした情報源リンク
- go.dev:
encoding/xml
パッケージのドキュメント - Stack Overflow:
encoding/xml
の関連質問と回答 - Go Issue Tracker (一般的な情報源として):
- https://github.com/golang/go/issues (ただし、Issue #3559は直接検索結果には現れませんでした。これは古いIssueであるか、内部的なトラッカーで管理されていた可能性があります。)