[インデックス 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であるか、内部的なトラッカーで管理されていた可能性があります。)