[インデックス 17093] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/xml
パッケージにおけるXMLマーシャリングの挙動を修正するものです。具体的には、構造体のフィールドがポインタ型であり、かつXML属性としてマーシャリングされる場合に、そのポインタが指す値が正しく処理されるように改善されています。特に、omitempty
タグが指定されている場合や、ポインタが指す値が空でない場合に、適切にXML属性として出力されるようになります。
コミット
commit 547f1a6fe7915193d6c28dac21648f08e2f67bd9
Author: Dmitriy Shelenin <deemok@googlemail.com>
Date: Thu Aug 8 10:40:51 2013 -0700
encoding/xml: allow attributes stored in pointers to be marshaled.
Fixes #5334.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/8653047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/547f1a6fe7915193d6c28dac21648f08e2f67bd9
元コミット内容
このコミットの目的は、「ポインタに格納された属性がマーシャリングされることを許可する」ことです。これは、Goのencoding/xml
パッケージが、構造体のフィールドがポインタ型であるXML属性を正しく処理できないというバグ(Issue 5334)を修正します。
変更の背景
この変更は、Goのencoding/xml
パッケージが抱えていた特定のバグ、Issue 5334を修正するために導入されました。このバグは、Goの構造体からXMLを生成する(マーシャリングする)際に、XML属性として扱われるフィールドがポインタ型である場合に発生していました。
具体的には、以下のような問題がありました。
omitempty
タグの誤った挙動: 構造体のフィールドがポインタ型で、かつxml:",omitempty"
タグが指定されている場合、ポインタがnil
でないにもかかわらず、そのポインタが指す値が「空」と判断されると、属性が出力されないという問題がありました。例えば、*string
型のフィールドが空文字列""
を指している場合、omitempty
が指定されていると属性が省略されてしまっていました。しかし、ポインタ自体はnil
ではないため、属性は出力されるべきでした。- ポインタのデリファレンス不足:
encoding/xml
パッケージのマーシャリングロジックが、ポインタ型のフィールドをXML属性として処理する際に、ポインタのデリファレンス(ポインタが指す実際の値へのアクセス)を適切に行っていませんでした。このため、ポインタが指す値ではなく、ポインタ自体が持つアドレス値のようなものが処理対象となり、期待通りのXML出力が得られませんでした。
これらの問題により、開発者はポインタ型のフィールドをXML属性として柔軟に利用することができず、不便を強いられていました。このコミットは、これらの問題を解決し、encoding/xml
パッケージの堅牢性と使いやすさを向上させることを目的としています。
前提知識の解説
このコミットの理解には、以下のGo言語およびXMLに関する知識が役立ちます。
-
Go言語の
encoding/xml
パッケージ:- Go言語でXMLデータを扱うための標準ライブラリです。
- マーシャリング (Marshaling): Goの構造体(struct)のデータをXML形式のバイト列に変換するプロセスを指します。
xml.Marshal
関数がこれを行います。 - アンマーシャリング (Unmarshaling): XML形式のバイト列をGoの構造体に変換するプロセスを指します。
xml.Unmarshal
関数がこれを行います。 - 構造体タグ (Struct Tags): 構造体のフィールドに付与されるメタデータです。
xml:"name,attr"
のように記述され、XML要素名、属性、またはその他のマーシャリング/アンマーシャリングの挙動を制御します。xml:"name"
: XML要素の名前を指定します。xml:"name,attr"
: フィールドがXML属性として扱われることを示します。xml:",omitempty"
: フィールドの値がGoの「ゼロ値」(数値の0、文字列の""、スライスのnilなど)である場合に、XML出力からその要素または属性を省略することを示します。xml:",chardata"
: フィールドがXML要素の文字データ(内部テキスト)として扱われることを示します。
-
Go言語の
reflect
パッケージ:- Goのランタイムリフレクション機能を提供するパッケージです。プログラムの実行中に型情報や値情報を動的に検査・操作することを可能にします。
reflect.Value
: Goの変数の値を表す型です。リフレクション操作の多くはこの型を通じて行われます。reflect.Kind
:reflect.Value
が表す値の具体的な種類(例:reflect.Int
,reflect.String
,reflect.Ptr
,reflect.Struct
など)を示します。reflect.Ptr
: 値がポインタであることを示すreflect.Kind
の定数です。reflect.Elem()
:reflect.Value
がポインタの場合、そのポインタが指す要素のreflect.Value
を返します。ポインタをデリファレンスする際に使用されます。ポインタでない場合はパニックを起こします。isEmptyValue(reflect.Value)
:encoding/xml
パッケージ内部で使われるヘルパー関数で、あるreflect.Value
がGoのゼロ値であるかどうかを判定します。この関数は、omitempty
タグの挙動を決定する際に重要です。
-
XML属性:
- XML要素の開始タグ内に記述される、要素に関する追加情報を提供するキーと値のペアです。例:
<element attribute_name="attribute_value">
- XML要素の開始タグ内に記述される、要素に関する追加情報を提供するキーと値のペアです。例:
これらの知識を前提として、コミットの変更点とそれが解決する問題について深く掘り下げていきます。
技術的詳細
このコミットの技術的詳細は、encoding/xml
パッケージのマーシャリングロジック、特にmarshal.go
内のprinter
構造体のmarshalValue
メソッドとmarshalStruct
メソッドにおける変更に集約されます。
marshalValue
メソッドの変更点
marshalValue
メソッドは、個々のフィールドの値をXMLにマーシャリングする役割を担っています。このメソッドにおける主要な変更点は、XML属性として扱われるフィールド(finfo.flags
にfAttr
フラグが立っているフィールド)の処理ロジックです。
変更前は、omitempty
が指定されている属性フィールドに対しては、isEmptyValue(fv)
がtrue
の場合にその属性をスキップしていました。しかし、ポインタ型のフィールドの場合、ポインタ自体がnil
でなくても、そのポインタが指す値が空(例: *string
が""
を指す)であればisEmptyValue
はtrue
を返していました。これにより、ポインタがnil
でないにもかかわらず属性が省略されるという問題が発生していました。
変更後のコードは以下のようになります。
// 変更前:
// if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) {
// continue
// }
// 変更後:
if (finfo.flags&fOmitEmpty != 0 || fv.Kind() == reflect.Ptr) && isEmptyValue(fv) {
continue
}
この変更により、omitempty
が指定されているか、またはフィールドがポインタ型である場合にisEmptyValue(fv)
がtrue
であればスキップするという条件になりました。一見すると、ポインタ型の場合に常にisEmptyValue
が評価されるように見えますが、これはポインタがnil
でない場合にisEmptyValue
がtrue
を返すケース(例: *string
が""
を指す)を考慮したものです。
さらに重要な変更は、属性値をマーシャリングする直前に行われるポインタのデリファレンスです。
// 追加されたコード:
// Handle pointer values by following the pointer,
// Pointer is known to be non-nil because we called isEmptyValue above.
if fv.Kind() == reflect.Ptr {
fv = fv.Elem()
}
if err := p.marshalSimple(fv.Type(), fv); err != nil {
return err
}
このコードブロックが追加されたことで、fv
(フィールドのreflect.Value
)がreflect.Ptr
型である場合、fv.Elem()
を呼び出してポインタが指す実際の値のreflect.Value
を取得し、それをfv
に再代入しています。これにより、p.marshalSimple
関数が呼び出される際には、ポインタ自体ではなく、ポインタが指す基底の型の値が渡されるようになります。コメントにあるように、この時点ではisEmptyValue
のチェックを通過しているため、ポインタはnil
ではないことが保証されています。
marshalStruct
メソッドの変更点
marshalStruct
メソッドは、構造体全体のマーシャリングを管理し、各フィールドを処理します。ここでの変更は、chardata
(XML要素のテキストコンテンツ)として扱われるフィールド、または通常のXML要素として扱われるフィールド(fMode
がfCharData
またはそれ以外)に対して、ポインタのデリファレンスを追加した点です。
// 追加されたコード:
// Handle pointer values by following the pointer
if vf.Kind() == reflect.Ptr && !isEmptyValue(vf) {
vf = vf.Elem()
}
この変更は、marshalValue
の変更と同様に、フィールドのreflect.Value
がポインタ型であり、かつそのポインタがnil
でない(!isEmptyValue(vf)
)場合に、vf.Elem()
を呼び出してポインタをデリファレンスするようにしています。これにより、chardata
や通常のXML要素としてマーシャリングされるポインタ型のフィールドも、正しくその指す値が処理されるようになります。
変更の全体的な影響
これらの変更により、encoding/xml
パッケージは、ポインタ型のフィールドがXML属性、XML要素の文字データ、または通常のXML要素として定義されている場合でも、そのポインタが指す実際の値を正確にマーシャリングできるようになりました。特に、omitempty
タグとポインタの組み合わせにおける誤った挙動が修正され、より直感的で期待通りのXML出力が得られるようになります。
コアとなるコードの変更箇所
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -271,7 +271,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
continue
}
fv := finfo.value(val)
- if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) {
+ if (finfo.flags&fOmitEmpty != 0 || fv.Kind() == reflect.Ptr) && isEmptyValue(fv) {
continue
}
p.WriteByte(' ')
@@ -285,6 +285,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
}
p.WriteString(finfo.name)
p.WriteString(`="`)
+ // Handle pointer values by following the pointer,
+ // Pointer is known to be non-nil because we called isEmptyValue above.
+ if fv.Kind() == reflect.Ptr {
+ fv = fv.Elem()
+ }
if err := p.marshalSimple(fv.Type(), fv); err != nil {
return err
}
@@ -363,6 +368,10 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
continue
}
vf := finfo.value(val)
+ // Handle pointer values by following the pointer
+ if vf.Kind() == reflect.Ptr && !isEmptyValue(vf) {
+ vf = vf.Elem()
+ }
switch finfo.flags & fMode {
case fCharData:
var scratch [64]byte
src/pkg/encoding/xml/marshal_test.go
--- a/src/pkg/encoding/xml/marshal_test.go
+++ b/src/pkg/encoding/xml/marshal_test.go
@@ -276,6 +276,25 @@ type Strings struct {
X []string `xml:"A>B,omitempty"`
}
+type PointerFieldsTest struct {
+ XMLName Name `xml:"dummy"`
+ Name *string `xml:"name,attr"`
+ Age *uint `xml:"age,attr"`
+ Empty *string `xml:"empty,attr"`
+ Contents *string `xml:",chardata"`
+}
+
+type ChardataEmptyTest struct {
+ XMLName Name `xml:"test"`
+ Contents *string `xml:",chardata"`
+}
+
+var (
+ nameAttr = "Sarah"
+ ageAttr = uint(12)
+ contentsAttr = "lorem ipsum"
+)
+
// Unless explicitly stated as such (or *Plain), all of the
// tests below are two-way tests. When introducing new tests,
// please try to make them two-way as well to ensure that
@@ -673,6 +692,20 @@ var marshalTests = []struct {
ExpectXML: `<OmitAttrTest></OmitAttrTest>`,
},
+ // pointer fields
+ {
+ Value: &PointerFieldsTest{Name: &nameAttr, Age: &ageAttr, Contents: &contentsAttr},
+ ExpectXML: `<dummy name="Sarah" age="12">lorem ipsum</dummy>`,
+ MarshalOnly: true,
+ },
+
+ // empty chardata pointer field
+ {
+ Value: &ChardataEmptyTest{},
+ ExpectXML: `<test></test>`,
+ MarshalOnly: true,
+ },
+
// omitempty on fields
{
Value: &OmitFieldTest{
コアとなるコードの解説
src/pkg/encoding/xml/marshal.go
の変更点
-
marshalValue
メソッド内のomitempty
条件の修正:- 変更前:
if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv)
- これは、「
omitempty
フラグが設定されており、かつ値が空である場合」に属性をスキップするという条件でした。ポインタ型の場合、ポインタがnil
でなくても、その指す値が空であればisEmptyValue
がtrue
を返し、属性が誤って省略される問題がありました。
- これは、「
- 変更後:
if (finfo.flags&fOmitEmpty != 0 || fv.Kind() == reflect.Ptr) && isEmptyValue(fv)
- この修正により、「
omitempty
フラグが設定されているか、または値がポインタ型である場合、かつ値が空である場合」に属性をスキップするという条件になりました。 - この変更の意図は、ポインタ型の場合に
isEmptyValue
がtrue
を返すケース(例:*string
が""
を指す)を正しく処理することにあります。ポインタがnil
でない限り、omitempty
が指定されていても属性は出力されるべきです。この修正は、ポインタがnil
の場合にのみ属性を省略するという、より直感的な挙動を実現します。
- この修正により、「
- 変更前:
-
marshalValue
メソッド内のポインタデリファレンス:- 追加されたコード:
if fv.Kind() == reflect.Ptr { fv = fv.Elem() }
- このコードは、
fv
(フィールドのreflect.Value
)がポインタ型(reflect.Ptr
)である場合に、fv.Elem()
を呼び出してポインタが指す実際の値のreflect.Value
を取得し、それをfv
に再代入しています。 - これにより、続く
p.marshalSimple(fv.Type(), fv)
の呼び出しでは、ポインタ自体ではなく、ポインタが指す基底の型の値がマーシャリングされるようになります。これは、XML属性の値としてポインタのアドレスではなく、実際の文字列や数値などを出力するために不可欠な変更です。
- 追加されたコード:
-
marshalStruct
メソッド内のポインタデリファレンス:- 追加されたコード:
if vf.Kind() == reflect.Ptr && !isEmptyValue(vf) { vf = vf.Elem() }
- この変更は、
marshalValue
と同様に、構造体のフィールドがポインタ型であり、かつそのポインタがnil
でない(!isEmptyValue(vf)
)場合に、vf.Elem()
を呼び出してポインタをデリファレンスするようにしています。 - この修正は、
chardata
(XML要素のテキストコンテンツ)や通常のXML要素としてマーシャリングされるポインタ型のフィールドに対しても、正しくその指す値が処理されるようにするために必要です。
- 追加されたコード:
src/pkg/encoding/xml/marshal_test.go
の変更点
-
新しいテスト構造体
PointerFieldsTest
の追加:type PointerFieldsTest struct { XMLName Name `xml:"dummy"` Name *string `xml:"name,attr"` Age *uint `xml:"age,attr"` Empty *string `xml:"empty,attr"` Contents *string `xml:",chardata"` }
- この構造体は、
*string
や*uint
といったポインタ型のフィールドをXML属性(name,attr
)や文字データ(,chardata
)として持つケースをテストするために定義されています。Empty
フィールドは、omitempty
の挙動をテストするために使用されます。
- この構造体は、
-
新しいテスト構造体
ChardataEmptyTest
の追加:type ChardataEmptyTest struct { XMLName Name `xml:"test"` Contents *string `xml:",chardata"` }
- この構造体は、ポインタ型の
chardata
フィールドが空の場合の挙動をテストするために使用されます。
- この構造体は、ポインタ型の
-
テストデータ
nameAttr
,ageAttr
,contentsAttr
の追加:- これらの変数は、
PointerFieldsTest
構造体のポインタフィールドに割り当てるための具体的な値を提供します。
- これらの変数は、
-
marshalTests
に新しいテストケースの追加:- ポインタフィールドのテストケース:
{ Value: &PointerFieldsTest{Name: &nameAttr, Age: &ageAttr, Contents: &contentsAttr}, ExpectXML: `<dummy name="Sarah" age="12">lorem ipsum</dummy>`, MarshalOnly: true, },
- このテストケースは、
PointerFieldsTest
のポインタフィールドに実際の値を設定し、期待されるXML出力が正しく生成されることを検証します。MarshalOnly: true
は、このテストがマーシャリングのみを対象とし、アンマーシャリングは行わないことを示します。
- このテストケースは、
- 空の
chardata
ポインタフィールドのテストケース:{ Value: &ChardataEmptyTest{}, ExpectXML: `<test></test>`, MarshalOnly: true, },
- このテストケースは、
ChardataEmptyTest
のContents
フィールドがポインタ型で、かつnil
である場合に、空のXML要素が生成されることを検証します。これは、omitempty
の挙動がポインタ型に対して正しく適用されることを確認するものです。
- このテストケースは、
- ポインタフィールドのテストケース:
これらのテストケースは、コミットによって修正された問題が実際に解決され、ポインタ型のフィールドがXML属性や文字データとして正しくマーシャリングされることを保証します。
関連リンク
- Go Issue 5334: encoding/xml: allow attributes stored in pointers to be marshaled.
- Go CL 8653047: encoding/xml: allow attributes stored in pointers to be marshaled.
参考にした情報源リンク
- Go Programming Language: The reflect package
- Go Programming Language: The encoding/xml package
- A Tour of Go: Structs
- Effective Go: Structs
- XML (Extensible Markup Language)
- Go言語のreflectパッケージについて
- Go言語のxmlパッケージで構造体をXMLに変換する
- Go言語のomitemptyタグの挙動