LoginSignup
1
0

More than 1 year has passed since last update.

XMLをスライスにUnmarshalするときの罠

Posted at

スライスを Marshal して Unmarshal すると、先頭要素しか取得できません

スライスをもとに戻せない?
package main

import (
	"encoding/xml"
	"fmt"
)

func main() {
	nums := []int{1, 2, 3}
	b, _ := xml.Marshal(nums)

	var copiedNums []int
	_ = xml.Unmarshal(b, &copiedNums)
	// 先頭要素しか取れない!
	fmt.Printf("%#v\n", copiedNums) // []int{1}
}

JSONのように MarshalUnmarshal を逆変換として使うことはできません。XMLをオブジェクトのシリアライズに使っている場合は注意が必要です。

バージョン

Go 1.18

Why?

スライスをmarshalすると各要素が並んだ形式で出力されます。

 []int{1, 2, 3} → "<int>1</int><int>2</int><int>3</int>"

しかし、unmarshalのデコーダーは最初の要素を読み終えた時点で完結したxmlと判断し終了してしまいます。

<int>1</int> ここでXMLが完結した! → []int{1}

その結果、元々いくつ要素があろうが先頭要素しか取得できません。

issueも上がっていますが、Go1の間は後方互換を保証するので実現は難しいかもしれません...

対処法

構造体でラップする

最上位の要素が1つであればいいので、[]int フィールドを持つ構造体をmarshal, unmarshalすれば全要素コピーできます。

package main

import (
	"encoding/xml"
	"fmt"
)

// ラッパー
type IntSlice struct {
	Nums []int
}

func main() {
	nums := IntSlice{[]int{1, 2, 3}}

	b, _ := xml.Marshal(nums)
	fmt.Println(string(b)) // "<IntSlice><Nums>1</Nums><Nums>2</Nums><Nums>3</Nums></IntSlice>"

	var copiedNums IntSlice
	// IntSliceタグで包まれるので、unmarshalはXMLを最後(IntSliceの閉じタグ)まで読んでくれる
	if err := xml.Unmarshal(b, &copiedNums); err != nil {
		fmt.Printf("error: %+v\n", err)
		return
	}

	fmt.Printf("%#v\n", copiedNums) // IntSlice{Nums:[]int{1, 2, 3}}
}

自分で Unmarshal を実装する

以下の記事では、スライスのdefined typeにUnmarshalメソッドを生やし、XMLを最後まで読み込むようにする方法が紹介されていました。

おわりに

機会があって、XMLを使ったディープコピーを検討していたらがっつりはまりました...
構文も良く分からずJSONのノリで使っていたしっぺ返しでもあるので、これを機に勉強しようと思います :innocent:

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0