[インデックス 11691] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/xml
パッケージに omitempty
フラグのサポートを追加し、それに伴い属性のマーシャリング動作を変更するものです。具体的には、空の文字列やバイトスライスも属性としてマーシャリングされるようになり、以前の動作に戻すためには omitempty
フラグを使用できるようになります。
コミット
commit 0a7ad329e17331a0ca4776b6b9ac67dfc32ad24d
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Wed Feb 8 01:57:44 2012 -0200
encoding/xml: add support for the omitempty flag
This also changes the behavior of attribute marshalling so
that strings and byte slices are marshalled even if empty.
The omitempty flag may be used to obtain the previous behavior.
Fixes #2899.
R=rsc
CC=golang-dev
https://golang.org/cl/5645050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0a7ad329e17331a0ca4776b6b9ac67dfc32ad24d
元コミット内容
encoding/xml
パッケージに omitempty
フラグのサポートを追加します。
これにより、属性のマーシャリング動作も変更され、空の文字列やバイトスライスもマーシャリングされるようになります。
以前の動作(空の場合はマーシャリングしない)に戻すには、omitempty
フラグを使用できます。
Issue #2899 を修正します。
変更の背景
この変更の背景には、Go言語の encoding/xml
パッケージにおけるXMLマーシャリングの柔軟性の向上が挙げられます。以前のバージョンでは、構造体のフィールドが空の文字列やバイトスライスである場合、XML属性としてマーシャリングされないという暗黙の挙動がありました。これは、特定のユースケースでは便利である一方で、XMLスキーマによっては空の属性も明示的に出力する必要がある場合や、一貫性のない挙動として認識される可能性がありました。
Issue #2899(コミットメッセージに記載されている)は、この挙動に関する問題提起であったと推測されます。ユーザーは、空の文字列やバイトスライスであってもXML属性として出力されることを期待していたか、あるいはその挙動を制御する手段を求めていたと考えられます。
このコミットは、以下の2つの主要な変更によってこの問題に対処しています。
omitempty
フラグの導入: JSONマーシャリングで広く使われているomitempty
フラグをXMLマーシャリングにも導入することで、フィールドが空の場合にその要素や属性を省略するかどうかを開発者が明示的に制御できるようになります。これにより、XML出力の柔軟性が向上し、特定のXMLスキーマへの準拠が容易になります。- 空の文字列/バイトスライスの属性マーシャリングの変更: デフォルトの挙動として、空の文字列やバイトスライスもXML属性としてマーシャリングされるように変更されました。これにより、より予測可能で一貫性のあるXML出力が得られるようになります。もし以前の「空の場合は省略する」挙動が必要な場合は、新しく導入された
omitempty
フラグを使用することで実現できます。
この変更は、Goの encoding/xml
パッケージがより堅牢で、多様なXML要件に対応できるようになるための重要なステップと言えます。
前提知識の解説
このコミットの変更内容を理解するためには、以下の前提知識が必要です。
1. Go言語の encoding/xml
パッケージ
encoding/xml
パッケージは、Goの構造体とXMLドキュメントの間でデータをエンコード(マーシャリング)およびデコード(アンマーシャリング)するための機能を提供します。
- マーシャリング (Marshalling): Goの構造体のデータをXML形式に変換するプロセスです。
xml.Marshal
関数がこれを行います。 - アンマーシャリング (Unmarshalling): XML形式のデータをGoの構造体に変換するプロセスです。
xml.Unmarshal
関数がこれを行います。 - 構造体タグ (Struct Tags): Goの構造体のフィールドに付与される文字列リテラルで、
encoding/xml
パッケージがXML要素名、属性名、その他のマーシャリング/アンマーシャリングの挙動を決定するために使用します。例えば、xml:"name,attr"
は、そのフィールドがXML属性name
に対応することを示します。
2. XMLの属性と要素
- XML要素:
<tag>content</tag>
のように、開始タグと終了タグで囲まれた構造です。 - XML属性: 開始タグ内に
name="value"
の形式で記述される、要素に関する追加情報です。
3. reflect
パッケージ
Goの reflect
パッケージは、実行時にプログラムの構造(型、フィールド、メソッドなど)を検査および操作するための機能を提供します。encoding/xml
パッケージのようなマーシャリングライブラリは、この reflect
パッケージを内部的に使用して、Goの構造体のフィールド情報を取得し、それに基づいてXMLを生成します。
reflect.Value
: 任意のGoの値の実行時データ表現です。reflect.Kind()
:reflect.Value
が表す値の基本的なカテゴリ(例:reflect.String
,reflect.Int
,reflect.Slice
,reflect.Ptr
など)を返します。reflect.Len()
: スライス、配列、マップ、文字列などの長さを持つ型の長さを返します。reflect.IsNil()
: ポインタ、インターフェース、マップ、スライス、チャネル、関数などの値がnil
であるかどうかを返します。
4. omitempty
の概念
omitempty
は、主にJSONマーシャリングで広く使われている概念です。構造体タグに json:"fieldName,omitempty"
のように指定することで、そのフィールドの値が「空」である場合に、JSON出力からそのフィールドを完全に省略するよう指示します。
「空」の定義は型によって異なります。
- 数値型 (int, floatなど): ゼロ値 (0)
- ブール型 (bool):
false
- 文字列型 (string): 空文字列 (
""
) - 配列、スライス、マップ: 長さがゼロ (
len == 0
) - ポインタ、インターフェース:
nil
このコミットでは、この omitempty
の概念を encoding/xml
パッケージにも導入し、XMLマーシャリングにおいても同様の挙動を可能にしています。
技術的詳細
このコミットの技術的な変更は、主に src/pkg/encoding/xml/marshal.go
と src/pkg/encoding/xml/typeinfo.go
の2つのファイルに集中しています。
1. omitempty
フラグの導入 (typeinfo.go
)
src/pkg/encoding/xml/typeinfo.go
では、構造体タグを解析してフィールドの情報を保持する fieldInfo
構造体に関連する変更が行われています。
fOmitEmpty
フラグの追加:const
ブロックにfOmitEmpty
という新しいフラグが追加されました。これは、フィールドがomitempty
オプションを持つことを示すビットフラグです。- タグ解析ロジックの更新:
structFieldInfo
関数内で、構造体タグのオプションを解析する部分が更新され、"omitempty"
オプションが検出された場合にfOmitEmpty
フラグがfieldInfo
に設定されるようになりました。 - フラグのバリデーション:
omitempty
フラグがfElement
(要素) またはfAttr
(属性) のいずれかと組み合わせて使用されていることを確認するためのバリデーションロジックが追加されました。これにより、omitempty
が意味をなさない他のモード(例:chardata
,comment
)と誤って組み合わされることを防ぎます。
2. isEmptyValue
ヘルパー関数の追加 (marshal.go
)
src/pkg/encoding/xml/marshal.go
に、isEmptyValue(v reflect.Value) bool
という新しいヘルパー関数が追加されました。この関数は、与えられた reflect.Value
が「空」であるかどうかを判断します。これは、omitempty
フラグの挙動を決定するために不可欠な関数です。
isEmptyValue
関数は、reflect.Value
の Kind()
に基づいて以下のように「空」を定義します。
reflect.Array
,reflect.Map
,reflect.Slice
,reflect.String
:v.Len() == 0
(長さがゼロ)reflect.Bool
:!v.Bool()
(false)reflect.Int
,reflect.Int8
, ...,reflect.Int64
:v.Int() == 0
(ゼロ)reflect.Uint
,reflect.Uint8
, ...,reflect.Uintptr
:v.Uint() == 0
(ゼロ)reflect.Float32
,reflect.Float64
:v.Float() == 0
(ゼロ)reflect.Interface
,reflect.Ptr
:v.IsNil()
(nil)
3. マーシャリングロジックの変更 (marshal.go
)
src/pkg/encoding/xml/marshal.go
の (p *printer) marshalValue
メソッドと、属性を処理する部分に主要な変更が加えられました。
- 要素の
omitempty
処理:marshalValue
関数の冒頭で、finfo.flags&fOmitEmpty != 0
(omitempty フラグが設定されている) かつisEmptyValue(val)
(値が空である) の場合、その値のマーシャリングをスキップするロジックが追加されました。 - 属性の
omitempty
処理とデフォルト挙動の変更: 属性を処理するループ内で、以前は文字列やバイトスライスが空の場合に属性をスキップするハードコードされたロジックがありました。このコミットでは、そのロジックが削除され、代わりにfinfo.flags&fOmitEmpty != 0 && isEmptyValue(fv)
の条件で属性をスキップするようになりました。これにより、omitempty
が指定されていない限り、空の文字列やバイトスライスも属性としてマーシャリングされるようになります。
4. テストケースの追加と修正 (marshal_test.go
, read_test.go
)
src/pkg/encoding/xml/marshal_test.go
には、omitempty
フラグの挙動を検証するための新しいテスト構造体 (OmitAttrTest
,OmitFieldTest
) と、それらを使用した多数のテストケースが追加されました。これにより、omitempty
が属性と要素の両方で正しく機能すること、および空の文字列/バイトスライスがデフォルトでマーシャリングされるようになった新しい挙動が確認されます。src/pkg/encoding/xml/read_test.go
では、既存のテスト構造体Port
,Domain
,Link
,Text
のxml
タグにomitempty
が追加され、新しいマーシャリング動作との整合性が保たれています。
これらの変更により、encoding/xml
パッケージはより柔軟になり、開発者はXML出力の制御を細かく行えるようになりました。
コアとなるコードの変更箇所
src/pkg/encoding/xml/marshal.go
--- a/src/pkg/encoding/xml/marshal.go
+++ b/src/pkg/encoding/xml/marshal.go
@@ -52,6 +52,10 @@ const (
// - a field with tag ",comment" is written as an XML comment, not
// subject to the usual marshalling procedure. It must not contain
// the "--" string within it.
+// - a field with a tag including the "omitempty" option is omitted
+// if the field value is empty. The empty values are false, 0, any
+// nil pointer or interface value, and any array, slice, map, or
+// string of length zero.
//
// If a field uses a tag "a>b>c", then the element c will be nested inside
// parent elements a and b. Fields that appear next to each other that name
@@ -63,6 +67,8 @@ const (
// FirstName string `xml:"person>name>first"`
// LastName string `xml:"person>name>last"`
// Age int `xml:"person>age"`
+// Height float `xml:"person>height,omitempty"`
+// Married bool `xml:"person>married"`
// }
//
// xml.Marshal(&Result{Id: 13, FirstName: "John", LastName: "Doe", Age: 42})
@@ -76,6 +82,7 @@ const (
// <last>Doe</last>
// </name>
// <age>42</age>
+// <married>false</married>
// </person>
// </result>
//
@@ -116,6 +123,9 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
if !val.IsValid() {
return nil
}
+ if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) {
+ return nil
+ }
kind := val.Kind()
typ := val.Type()
@@ -183,12 +193,8 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
continue
}
fv := val.FieldByIndex(finfo.idx)
-\t\tswitch fv.Kind() {\n-\t\tcase reflect.String, reflect.Array, reflect.Slice:\n-\t\t\t// TODO: Should we really do this once ,omitempty is in?\n-\t\t\tif fv.Len() == 0 {\n-\t\t\t\tcontinue\n-\t\t\t}\n+\t\tif finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) {
+\t\t\tcontinue
}
p.WriteByte(' ')
p.WriteString(finfo.name)
@@ -378,3 +384,21 @@ type UnsupportedTypeError struct {
func (e *UnsupportedTypeError) Error() string {
return "xml: unsupported type: " + e.Type.String()
}
+
+func isEmptyValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ }
+ return false
+}
src/pkg/encoding/xml/typeinfo.go
--- a/src/pkg/encoding/xml/typeinfo.go
+++ b/src/pkg/encoding/xml/typeinfo.go
@@ -36,8 +36,7 @@ const (
fComment
fAny
-// TODO:
-//fOmitEmpty
+ fOmitEmpty
fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny
)
@@ -133,20 +132,28 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
case "any":
finfo.flags |= fAny
+ case "omitempty":
+ finfo.flags |= fOmitEmpty
}
}
// Validate the flags used.
+ valid := true
switch mode := finfo.flags & fMode; mode {
case 0:
finfo.flags |= fElement
case fAttr, fCharData, fInnerXml, fComment, fAny:
-\t\t\tif f.Name != "XMLName" && (tag == "" || mode == fAttr) {
-\t\t\t\tbreak
+\t\t\tif f.Name == "XMLName" || tag != "" && mode != fAttr {
+\t\t\t\tvalid = false
}
-\t\t\tfallthrough
default:
// This will also catch multiple modes in a single field.
+ valid = false
+ }
+ if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
+ valid = false
+ }
+ if !valid {
return nil, fmt.Errorf("xml: invalid tag in field %s of type %s: %q",
f.Name, typ, f.Tag.Get("xml"))
}
コアとなるコードの解説
src/pkg/encoding/xml/marshal.go
の変更点
-
omitempty
のドキュメント追加:xml.Marshal
のドキュメントにomitempty
フラグに関する説明が追加されました。これにより、開発者はこの新しいオプションの存在とその挙動を理解できます。 -
marshalValue
関数におけるomitempty
処理:func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error
関数は、Goの構造体の個々のフィールドの値をXMLにマーシャリングする主要なロジックを含んでいます。 追加された以下の行が重要です。if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) { return nil }
これは、フィールド情報 (
finfo
) が存在し、そのフィールドにfOmitEmpty
フラグが設定されており、かつisEmptyValue(val)
がtrue
(つまり、フィールドの値が「空」である) の場合、そのフィールドのマーシャリングを完全にスキップすることを示しています。これにより、omitempty
タグが指定されたフィールドが空の場合にXML出力から除外されます。 -
属性マーシャリングロジックの変更:
marshalValue
関数内の属性を処理する部分で、以前存在した以下のコードが削除されました。switch fv.Kind() { case reflect.String, reflect.Array, reflect.Slice: // TODO: Should we really do this once ,omitempty is in? if fv.Len() == 0 { continue }
この削除されたコードは、文字列、配列、スライス型のフィールドが空の場合に、その属性のマーシャリングをスキップするというものでした。この挙動は、
omitempty
フラグが導入される前のデフォルトの挙動でした。 代わりに、以下のコードが追加されました。if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { continue }
この変更により、属性のマーシャリングは
omitempty
フラグとisEmptyValue
関数によって制御されるようになりました。つまり、omitempty
が指定されていない限り、空の文字列やバイトスライスも属性としてマーシャリングされるようになります。これにより、以前の暗黙的なスキップ挙動が明示的な制御に置き換わりました。 -
isEmptyValue
ヘルパー関数の追加: この新しい関数は、reflect.Value
が表す値が「空」であるかどうかを判断するための汎用的なロジックを提供します。Goの様々な組み込み型(数値、ブール、文字列、スライス、マップ、ポインタ、インターフェース)に対して、それぞれの「空」の定義に基づいてtrue
またはfalse
を返します。この関数は、omitempty
フラグの評価において中心的な役割を果たします。
src/pkg/encoding/xml/typeinfo.go
の変更点
-
fOmitEmpty
フラグの定義:const
ブロックにfOmitEmpty
という新しいビットフラグが追加されました。これは、構造体タグの解析時にomitempty
オプションが検出された場合に、フィールドのflags
に設定されます。 -
構造体タグ解析の更新:
func structFieldInfo(...)
関数は、構造体のフィールドタグを解析し、そのフィールドに関する情報 (fieldInfo
) を構築します。この関数内で、タグオプションの解析ロジックが更新され、"omitempty"
という文字列が検出された場合にfinfo.flags |= fOmitEmpty
が実行されるようになりました。 -
フラグのバリデーションロジックの追加: タグの解析後、
fOmitEmpty
フラグがfElement
(要素) またはfAttr
(属性) のいずれかと組み合わせて使用されていることを確認するためのバリデーションが追加されました。if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { valid = false }
これは、
omitempty
が要素または属性にのみ適用可能であり、chardata
やcomment
のような他のXMLマーシャリングモードでは意味をなさないためです。これにより、不正なタグの組み合わせがコンパイル時に検出され、エラーが報告されるようになります。
これらの変更により、encoding/xml
パッケージは omitempty
フラグを認識し、その指示に従ってXML出力を生成する能力を獲得しました。また、空の文字列やバイトスライス属性のデフォルトの挙動が変更され、より明示的な制御が可能になりました。
関連リンク
- Go言語の
encoding/xml
パッケージ公式ドキュメント: https://pkg.go.dev/encoding/xml - Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect
参考にした情報源リンク
- コミットメッセージ内の Go CL (Change List) リンク: https://golang.org/cl/5645050
- コミットメッセージ内の Issue #2899 (Go issue tracker): https://go.dev/issue/2899 (直接的な検索では見つかりませんでしたが、コミットメッセージに記載されているため、このコミットが修正した問題として参照します。)