krak3n
krak3n

Reputation: 966

Go: XML Unmarshal Nested Structs into interface{}

I come from a Python background and this is my first proper foray into Go so I don't think things are clicking quite yet.

I'm currently implementing the Affiliate Window XML API in Go. The API follows a standard structure for Requests and Responses so to that end I'm trying to keep things dry. Envelopes always have the same structure, something like this:

<Envelope>
    <Header></Header>
    <Body></Body>
</Envelope>

The contents Header and Body will be different depending on what I'm requesting and the response so I created a base Envelope struct

type Envelope struct {
    XMLName xml.Name    `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
    NS1     string      `xml:"xmlns:ns1,attr"`
    XSD     string      `xml:"xmlns:xsd,attr"`
    Header  interface{} `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"`
    Body    interface{} `xml:"Body"`
}

This works well for marshaling the XML for a request but I'm having problems with unmarshaling:

func NewResponseEnvelope(body interface{}) *Envelope {
    envelope := NewEnvelope()
    envelope.Header = &ResponseHeader{}
    envelope.Body = body
    return envelope
}

func main() {
    responseBody := &GetMerchantListResponseBody{}
    responseEnvelope := NewResponseEnvelope(responseBody)

    b := bytes.NewBufferString(response)
    xml.NewDecoder(b).Decode(responseEnvelope)
    fmt.Println(responseEnvelope.Header.Quota) // Why can't I access this?
}

This http://play.golang.org/p/v-MkfEyFPM probably describes the problem better in code than I can in words :p

Thanks,

Chris

Upvotes: 2

Views: 612

Answers (1)

icza
icza

Reputation: 417777

The type of the Header field inside the Envelope struct is interface{} which is not a struct so you cannot refer to any of its fields.

In order to refer to a field named Quota, you have to declare Header with a static type which contains a Quota field, something like this:

type HeaderStruct struct {
    Quota string
}

type Envelope struct {
    // other fields omitted
    Header HeaderStruct
}

If you don't know what type it will be or you can't commit to a single type, you can leave it as interface{}, but then you have to use either Type switches or Type assertion to convert it to a static type at runtime, the latter would look something like this:

headerStruct, ok := responseEnvelope.Header.(HeaderStruct)
// if ok is true, headerStruct is of type HeaderStruct
// else responseEnvelope.Header is not of type HeaderStruct

Another option would be to use reflection to access named fields of the Envelope.Header value, but try to solve it in other ways if possible. If you're interested in learning more about reflection in Go, I recommend reading The Laws of Reflection blog post first.

Upvotes: 2

Related Questions