将XML元素名称解组为其他属性

问题描述:

I am currently writing a library for the NameSilo API. I am stuck on the getPriceList api, which returns XML like this:

<namesilo>
    <request>
        <operation>getPrices</operation>
        <ip>55.555.55.55</ip>
    </request>
    <reply>
        <code>300</code>
        <detail>success</detail>
        <com>
            <registration>8.99</registration>
            <transfer>8.39</transfer>
            <renew>8.99</renew>
        </com>
        <net>
            <registration>9.29</registration>
            <transfer>8.99</transfer>
            <renew>9.29</renew>
        </net>
    </reply>
</namesilo>

As you can see, there is an element for each TLD. I would like to unmarshal the element name (example: com, net) into a property which is not called XMLName (I want it to be called TLD).

After reading lines 34-39 of https://golang.org/src/encoding/xml/marshal.go, it seems like this is not possible.

I have tried the following code, but it does not work.

type APIResponse struct {
    Request struct {
        Operation string `xml:"operation"`
        IP        string `xml:"ip"`
    } `xml:"request"`
}

type GetPricesResponse struct {
    APIResponse
    Reply []struct {
        Domains []struct {
            TLD xml.name
            Registration string `xml:"registration"`
            Transfer     string `xml:"transfer"`
            Renew        string `xml:"renew"`
        } `xml:",any"`
    } `xml:"reply"`
}

Is there any way I can do this, or is it not possible to have a property name other than XMLName for the xml element name.

UPDATE: I looked a bit more into the code, and I found this, which makes me think I cannot do this easily.

There's no simpler alternative to XMLName xml.Name.

It's possible to do what you want with a type satisfying the unmarshaller interface. The added complexity is probably not worthwhile. Playground example:

package main

import (
    "encoding/xml"
    "fmt"
    "log"
)

func main() {
    var data Data
    if err := xml.Unmarshal(payload, &data); err != nil {
        log.Fatal(err)
    }

    for k, v := range data.Reply.Domains {
        fmt.Printf("%d: %#v
", k, v)
    }

}

type Domain struct {
    TLD          string
    Registration string `xml:"registration"`
    Transfer     string `xml:"transfer"`
    Renew        string `xml:"renew"`
}

func (domain *Domain) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    v := struct {
        XMLName      xml.Name
        Registration string `xml:"registration"`
        Transfer     string `xml:"transfer"`
        Renew        string `xml:"renew"`
    }{}
    d.DecodeElement(&v, &start)

    domain.TLD = v.XMLName.Local
    domain.Registration = v.Registration
    domain.Transfer = v.Transfer
    domain.Renew = v.Renew

    return nil
}

type Data struct {
    Request struct {
        Operation string `xml:"operation"`
        Ip        string `xml:"ip"`
    } `xml:"request"`
    Reply struct {
        Code    string   `xml:"code"`
        Detail  string   `xml:"detail"`
        Domains []Domain `xml:",any"`
    } `xml:"reply"`
}

var payload = []byte(`<namesilo>
    <request>
        <operation>getPrices</operation>
        <ip>55.555.55.55</ip>
    </request>
    <reply>
        <code>300</code>
        <detail>success</detail>
        <com>
            <registration>8.99</registration>
            <transfer>8.39</transfer>
            <renew>8.99</renew>
        </com>
        <net>
            <registration>9.29</registration>
            <transfer>8.99</transfer>
            <renew>9.29</renew>
        </net>
    </reply>
</namesilo>`)

You don't need to unmarshal into your final type. You could unmarshal into a separate set of types which mirror this data structure, then just translated into your preferred representation for use elsewhere (for example for fast lookup you might want to have a map[string]PriceRecord mapping domain names to Price records). I'd think of it that way rather than necessarily trying to do the translation in one step, which ties you completely to whatever xml they choose to produce - if it changes, your data structures would have to change too.