If you can't change the structure of xml
data, then defer unmarshaling rowset
with the following steps (Working example can be found at Go Playground):
- Unmarshal elements except
rowset
which are related to victim
. During this step, the rowset
will be unmarshaled into raw XML data.
- Decode raw XML separately using
Decoder.DecodeElement
.
First declare the data structure as follows (declaration of Victim
, Attacker
, Item
is omitted):
type Kill struct{
KillID int64 `xml:"killID,attr"`
Hash string
SolarSystemID int64 `xml:"solarSystemID,attr"`
MoonID int64 `xml:"moonID,attr"`
Victim Victim `xml:"victim"`
RawAttackersItems []byte `xml:",innerxml" json:"-"`
Attackers []Attacker `xml:"-"`
Items []Item `xml:"-"`
}
type Kills struct {
Kills []Kill `xml:"result>rowset>row"`
}
Next, code lines for unmarshaling the xml:
v := &Kills{}
if err := xml.Unmarshal([]byte(xmlText()), v); err != nil {
fmt.Printf("Error unmarshaling: %v
", err)
return
}
for i, k := range v.Kills {
v.Kills[i].Attackers, v.Kills[i].Items = decodeAttackerAndItems(k.RawAttackersItems)
}
and finally the decoder function:
func decodeAttackerAndItems(data []byte) ([]Attacker, []Item) {
xmlReader := bytes.NewReader(data)
decoder := xml.NewDecoder(xmlReader)
const (
unknown int = iota
attackers
items
)
rowset := unknown
attackerList := []Attacker{}
itemList := []Item{}
for {
t, _ := decoder.Token()
if t == nil {
break
}
switch se := t.(type) {
case xml.StartElement:
if se.Name.Local == "rowset" {
rowset = unknown
for _, attr := range se.Attr {
if attr.Name.Local == "name" {
if attr.Value == "attackers" {
rowset = attackers
break
} else if attr.Value == "items" {
rowset = items
break
}
}
}
} else if se.Name.Local == "row" {
switch rowset {
case attackers:
a := Attacker{}
if err := decoder.DecodeElement(&a, &se); err == nil {
attackerList = append(attackerList, a)
}
case items:
it := Item{}
if err := decoder.DecodeElement(&it, &se); err == nil {
itemList = append(itemList, it)
}
}
}
}
}
return attackerList, itemList
}