Search code examples
xmlgounmarshalling

Unmarshaling nested xml golang


I want to unmarshall a nested xml to a golang struct. The xml that I want to unmarshall is shown below.

<?xml version="1.0" encoding="UTF-8"?>
<status>
    <authorized>true</authorized>
    <plan>Basic</plan>
    <usage_reports>
        <usage_report metric="hits" period="day">
            <period_start>2021-07-20 00:00:00 +0000</period_start>
            <period_end>2021-07-21 00:00:00 +0000</period_end>
            <max_value>1000000</max_value>
            <current_value>0</current_value>
        </usage_report>
        <usage_report metric="hits.82343" period="day">
            <period_start>2021-07-20 00:00:00 +0000</period_start>
            <period_end>2021-07-21 00:00:00 +0000</period_end>
            <max_value>1000000</max_value>
            <current_value>0</current_value>
        </usage_report>
    </usage_reports>
</status>

To unmarshall I have defined two go structs as follows.

// UsageReport represents the usage report of a particular metric.
type UsageReport struct {
    Usage   string `xml:"usage_report,chardata"`
    Metric  string `xml:"metric,attr"`
    Period  string `xml:"period,attr"`
    Start   string `xml:"period_start"`
    End     string `xml:"period_end"`
    Max     int64  `xml:"max_value"`
    Current int64  `xml:"current_value"`
}

// AuthResponse represents the structure of the response from authorize calls.
type AuthResponse struct {
    Status     xml.Name      `xml:"status"`
    Authorized bool          `xml:"authorized"`
    Plan       string        `xml:"plan"`
    Usages     []UsageReport `xml:"usage_reports"`
}

But when I try to unmarshall using xml.Unmarshal, only Plan and Authorized values gets unmarshalled. The result of unmarshalling is shown below.

XML: {{ } true Basic []}

Solution

  • Try this go playground. I use this tool to cheat and help me generate Go types from XML data.

    package main
    
    import (
        "encoding/xml"
        "fmt"
    )
    
    type Status struct {
        XMLName      xml.Name `xml:"status"`
        Text         string   `xml:",chardata"`
        Authorized   string   `xml:"authorized"`
        Plan         string   `xml:"plan"`
        UsageReports struct {
            Text        string `xml:",chardata"`
            UsageReport []struct {
                Text         string `xml:",chardata"`
                Metric       string `xml:"metric,attr"`
                Period       string `xml:"period,attr"`
                PeriodStart  string `xml:"period_start"`
                PeriodEnd    string `xml:"period_end"`
                MaxValue     string `xml:"max_value"`
                CurrentValue string `xml:"current_value"`
            } `xml:"usage_report"`
        } `xml:"usage_reports"`
    }
    
    func main() {
        status := Status{}
        err := xml.Unmarshal([]byte(xmlStr), &status)
        if err != nil {
            panic(err)
        }
        fmt.Println(status.UsageReports.UsageReport[0].MaxValue)
    }
    
    const xmlStr = `<?xml version="1.0" encoding="UTF-8"?><status><authorized>true</authorized><plan>Basic</plan><usage_reports><usage_report metric="hits" period="day"><period_start>2021-07-20 00:00:00 +0000</period_start><period_end>2021-07-21 00:00:00 +0000</period_end><max_value>1000000</max_value><current_value>0</current_value></usage_report><usage_report metric="hits.82343" period="day"><period_start>2021-07-20 00:00:00 +0000</period_start><period_end>2021-07-21 00:00:00 +0000</period_end><max_value>1000000</max_value><current_value>0</current_value></usage_report></usage_reports></status>`