スライスを 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のように Marshal
と Unmarshal
を逆変換として使うことはできません。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のノリで使っていたしっぺ返しでもあるので、これを機に勉強しようと思います