読者です 読者をやめる 読者になる 読者になる

chikuchikugonzalezの雑記帳

趣味とか日記とかメモとか(∩゚д゚)

Goで構造体スライスをXMLにして ( ;゚皿゚)ノシΣバンバン!! ってなった話

開発 Go

結論から書くと、ちゃんと構造体でラップしましょう、ということです。
出ないと書き出したXMLがちゃんと読み込まれなくて ( ;゚皿゚)ノシΣバンバン!! ってすることになります。

やらかしたこと

次のように、構造体とそのスライスを用意して、XMLに変換したわけです。

package main

import "encoding/xml"
import "fmt"

type Item struct {
	XMLName xml.Name `xml:"item"`
	ID      string   `xml:"id,attr"`
	Name    string   `xml:"name"`
	Value   string   `xml:"value"`
}

type Items []Item

func main() {
	items := Items{
		Item{
			ID:    "001",
			Name:  "AA1",
			Value: "|ω・`)ノ ヤァ",
		},
		Item{
			ID:    "002",
			Name:  "AA2",
			Value: "|д゚)チラッ",
		},
	}

	if buf, err := xml.MarshalIndent(items, "", " "); err != nil {
		panic(err)
	} else {
		fmt.Print(xml.Header)
		fmt.Println(string(buf))
	}
}

実行すると次のようなXMLを得られます。

<?xml version="1.0" encoding="UTF-8"?>
<item id="001">
 <name>AA1</name>
 <value>|ω・`)ノ ヤァ</value>
</item>
<item id="002">
 <name>AA2</name>
 <value>|д゚)チラッ</value>
</item>

この時点ですでになんか怪しいことにお気づきでしょうか。そう、ルート要素が複数あるということに。

さて、これを以下のコードで読み込むと( ゚д゚)します。

package main

import "encoding/xml"
import "fmt"

type Item struct {
	XMLName xml.Name `xml:"item"`
	ID      string   `xml:"id,attr"`
	Name    string   `xml:"name"`
	Value   string   `xml:"value"`
}

type Items []Item

func main() {
	items := make(Items, 0)
	doc := `<?xml version="1.0" encoding="UTF-8"?>
<item id="001">
 <name>AA1</name>
 <value>|ω・`)ノ ヤァ</value>
</item>
<item id="002">
 <name>AA2</name>
 <value>|д゚)チラッ</value>
</item>
`
	if err := xml.Unmarshal([]byte(doc), &items); err != nil {
		panic(err)
	} else {
		for _, item := range items {
			fmt.Println(item)
		}
	}
}

以下出力結果。

{{ item} 001 AA1 |ω・`)ノ ヤァ}

二要素目が闇に消えましたね ( ・´ー・`)

原因

たぶんルート要素がいっぱいあるせい。つまり正しいXMLになってない。

正しい解決法

なので、ちゃんと構造体でラップしましょう、そうしましょう。

エンコードする方のコード
package main

import "encoding/xml"
import "fmt"

type Item struct {
	XMLName xml.Name `xml:"item"`
	ID      string   `xml:"id,attr"`
	Name    string   `xml:"name"`
	Value   string   `xml:"value"`
}

type Items struct {
	XMLName xml.Name `xml:"items"`
	Items   []Item   `xml:"item"`
}

func main() {
	items := &Items{
		Items: []Item{
			Item{
				ID:    "001",
				Name:  "AA1",
				Value: "|ω・`)ノ ヤァ",
			},
			Item{
				ID:    "002",
				Name:  "AA2",
				Value: "|д゚)チラッ",
			},
		},
	}

	if buf, err := xml.MarshalIndent(items, "", " "); err != nil {
		panic(err)
	} else {
		fmt.Print(xml.Header)
		fmt.Println(string(buf))
	}
}
エンコード結果
<?xml version="1.0" encoding="UTF-8"?>
<items>
 <item id="001">
  <name>AA1</name>
  <value>|ω・`)ノ ヤァ</value>
 </item>
 <item id="002">
  <name>AA2</name>
  <value>|д゚)チラッ</value>
 </item>
</items>
デコードする方のコード
package main

import "encoding/xml"
import "fmt"

type Item struct {
	XMLName xml.Name `xml:"item"`
	ID      string   `xml:"id,attr"`
	Name    string   `xml:"name"`
	Value   string   `xml:"value"`
}

type Items struct {
	XMLName xml.Name `xml:"items"`
	Items   []Item   `xml:"item"`
}

func main() {
	items := Items{}
	doc := `<?xml version="1.0" encoding="UTF-8"?>
<items>
 <item id="001">
  <name>AA1</name>
  <value>|ω・`)ノ ヤァ</value>
 </item>
 <item id="002">
  <name>AA2</name>
  <value>|д゚)チラッ</value>
 </item>
</items>
`
	if err := xml.Unmarshal([]byte(doc), &items); err != nil {
		panic(err)
	} else {
		for _, item := range items.Items {
			fmt.Println(item)
		}
	}
}
デコード結果
{{ item} 001 AA1 |ω・`)ノ ヤァ}
{{ item} 002 AA2 |д゚)チラッ}

ダメな解決方法

構造体作りたくないでござる、スライスはスライスのままにしたいでござる、みたいな人 *1 のためにたぶん間違った解決方法を。
その方法は xml.Marshaler / xml.Unmarshalerを実装して、ルート要素を無理やり作る、というのもですが。

エンコード
package main

import "encoding/xml"
import "fmt"

type Item struct {
	XMLName xml.Name `xml:"item"`
	ID      string   `xml:"id,attr"`
	Name    string   `xml:"name"`
	Value   string   `xml:"value"`
}

type Items []Item

func (self Items) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	root := struct {
		Items []Item `xml:"item"`
	}{
		Items: self,
	}

	start.Name.Local = "items"
	return e.EncodeElement(&root, start)
}

func main() {
	items := Items{
		Item{
			ID:    "001",
			Name:  "AA1",
			Value: "|ω・`)ノ ヤァ",
		},
		Item{
			ID:    "002",
			Name:  "AA2",
			Value: "|д゚)チラッ",
		},
	}

	if buf, err := xml.MarshalIndent(items, "", " "); err != nil {
		panic(err)
	} else {
		fmt.Print(xml.Header)
		fmt.Println(string(buf))
	}
}
デコード側
package main

import "encoding/xml"
import "fmt"

type Item struct {
	XMLName xml.Name `xml:"item"`
	ID      string   `xml:"id,attr"`
	Name    string   `xml:"name"`
	Value   string   `xml:"value"`
}

type Items []Item

func (self *Items) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	root := struct {
		Items []Item `xml:"item"`
	}{}

	if err := d.DecodeElement(&root, &start); err != nil {
		return err
	} else {
		*self = root.Items
		return nil
	}
}

func main() {
	items := Items{}
	doc := `<?xml version="1.0" encoding="UTF-8"?>
<items>
 <item id="001">
  <name>AA1</name>
  <value>|ω・`)ノ ヤァ</value>
 </item>
 <item id="002">
  <name>AA2</name>
  <value>|д゚)チラッ</value>
 </item>
</items>
`
	if err := xml.Unmarshal([]byte(doc), &items); err != nil {
		panic(err)
	} else {
		for _, item := range items {
			fmt.Println(item)
		}
	}
}

自分自身のスライスの参照先を書き換えるとか ((((;゚Д゚))))ガクガクブルブル しますね!!

*1: