I'm new to Golang and parsing XML with nested nodes of the same name is too difficult to me. This is a XML pulled from a third party API:
<?xml version="1.0" encoding="UTF-8"?>
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time="2019-01-28">
<Cube currency="USD" rate="1.1418"/>
<Cube currency="JPY" rate="124.94"/>
<Cube currency="BGN" rate="1.9558"/>
</Cube>
<Cube time="2019-01-25">
<Cube currency="USD" rate="1.1346"/>
<Cube currency="JPY" rate="124.72"/>
<Cube currency="BGN" rate="1.9558"/>
</Cube>
</Cube>
</gesmes:Envelope>
I need to parse it so I have an output like this:
&{Rates:[{Currency:USD Rate:1.1418 Date:2019-01-28} {Currency:JPY Rate:124.94 Date:2019-01-28} {Currency:BGN Rate:1.9558 Date:2019-01-28} {Currency:USD Rate:1.1346 Date:2019-01-25} {Currency:JPY Rate:124.72 Date:2019-01-25} {Currency:BGN Rate:1.9558 Date:2019-01-25}]}
And here's my code:
package main
import (
"encoding/xml"
"fmt"
)
type Rate struct {
Currency string `xml:"currency,attr"`
Rate string `xml:"rate,attr"`
Date string `xml:"time,attr"`
}
type Rates struct {
Rates []Rate `xml:"Cube>Cube>Cube"`
}
func main() {
v := &Rates{}
if err := xml.Unmarshal([]byte(src), v); err != nil {
panic(err)
}
fmt.Printf("%+v
", v)
}
const src = `<?xml version="1.0" encoding="UTF-8"?>
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
<gesmes:subject>Reference rates</gesmes:subject>
<gesmes:Sender>
<gesmes:name>European Central Bank</gesmes:name>
</gesmes:Sender>
<Cube>
<Cube time="2019-01-28">
<Cube currency="USD" rate="1.1418"/>
<Cube currency="JPY" rate="124.94"/>
<Cube currency="BGN" rate="1.9558"/>
</Cube>
<Cube time="2019-01-25">
<Cube currency="USD" rate="1.1346"/>
<Cube currency="JPY" rate="124.72"/>
<Cube currency="BGN" rate="1.9558"/>
</Cube>
</Cube>
</gesmes:Envelope>`
I'm out of ideas how to insert time
attribute into Rates object. Any help would be appreciated.
Here it is in golang playground
You could implement a custom xml.Unmarshaler
to get the results you want.
type Rate struct {
Currency string `xml:"currency,attr"`
Rate string `xml:"rate,attr"`
Date string `xml:"time,attr"`
}
type Rates struct {
Rates RateList `xml:"Cube>Cube"`
}
type RateList []Rate
func (ls *RateList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
date := start.Attr[0].Value
for {
tok, err := d.Token()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
if se, ok := tok.(xml.StartElement); ok {
rate := Rate{Date: date}
if err := d.DecodeElement(&rate, &se); err != nil {
return err
}
*ls = append(*ls, rate)
}
}
}
I agree with mkopriva's answer, but cannot comment so I'm adding a reply.
Make sure that you either trust the XML, or that you validate/sanitize it beforehand, with something like XSD. This should be done for both security reasons, as well as to be able to look back and see if the data you "Unmarshalled" is correct, because when you're processing huge amounts of XML, some of it is bound to be broken.