Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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つの主要な変更によってこの問題に対処しています。

  1. omitempty フラグの導入: JSONマーシャリングで広く使われている omitempty フラグをXMLマーシャリングにも導入することで、フィールドが空の場合にその要素や属性を省略するかどうかを開発者が明示的に制御できるようになります。これにより、XML出力の柔軟性が向上し、特定のXMLスキーマへの準拠が容易になります。
  2. 空の文字列/バイトスライスの属性マーシャリングの変更: デフォルトの挙動として、空の文字列やバイトスライスも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.gosrc/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.ValueKind() に基づいて以下のように「空」を定義します。

  • 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, Textxml タグに 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 の変更点

  1. omitempty のドキュメント追加: xml.Marshal のドキュメントに omitempty フラグに関する説明が追加されました。これにより、開発者はこの新しいオプションの存在とその挙動を理解できます。

  2. 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出力から除外されます。

  3. 属性マーシャリングロジックの変更: 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 が指定されていない限り、空の文字列やバイトスライスも属性としてマーシャリングされるようになります。これにより、以前の暗黙的なスキップ挙動が明示的な制御に置き換わりました。

  4. isEmptyValue ヘルパー関数の追加: この新しい関数は、reflect.Value が表す値が「空」であるかどうかを判断するための汎用的なロジックを提供します。Goの様々な組み込み型(数値、ブール、文字列、スライス、マップ、ポインタ、インターフェース)に対して、それぞれの「空」の定義に基づいて true または false を返します。この関数は、omitempty フラグの評価において中心的な役割を果たします。

src/pkg/encoding/xml/typeinfo.go の変更点

  1. fOmitEmpty フラグの定義: const ブロックに fOmitEmpty という新しいビットフラグが追加されました。これは、構造体タグの解析時に omitempty オプションが検出された場合に、フィールドの flags に設定されます。

  2. 構造体タグ解析の更新: func structFieldInfo(...) 関数は、構造体のフィールドタグを解析し、そのフィールドに関する情報 (fieldInfo) を構築します。この関数内で、タグオプションの解析ロジックが更新され、"omitempty" という文字列が検出された場合に finfo.flags |= fOmitEmpty が実行されるようになりました。

  3. フラグのバリデーションロジックの追加: タグの解析後、fOmitEmpty フラグが fElement (要素) または fAttr (属性) のいずれかと組み合わせて使用されていることを確認するためのバリデーションが追加されました。

    	if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
    		valid = false
    	}
    

    これは、omitempty が要素または属性にのみ適用可能であり、chardatacomment のような他のXMLマーシャリングモードでは意味をなさないためです。これにより、不正なタグの組み合わせがコンパイル時に検出され、エラーが報告されるようになります。

これらの変更により、encoding/xml パッケージは omitempty フラグを認識し、その指示に従ってXML出力を生成する能力を獲得しました。また、空の文字列やバイトスライス属性のデフォルトの挙動が変更され、より明示的な制御が可能になりました。

関連リンク

参考にした情報源リンク

  • コミットメッセージ内の Go CL (Change List) リンク: https://golang.org/cl/5645050
  • コミットメッセージ内の Issue #2899 (Go issue tracker): https://go.dev/issue/2899 (直接的な検索では見つかりませんでしたが、コミットメッセージに記載されているため、このコミットが修正した問題として参照します。)