Golang xml.Unmarshal接口类型

时间:2015-11-02 20:54:03

标签: xml go

在golang中使用xml包我无法解组非同类型列表。请考虑以下XML文档,其嵌套元素是非同类型列表:

<mydoc>
  <foo>Foo</foo>
  <bar>Bar</bar>
  <foo>Another Foo</foo>
  <foo>Foo #3</foo>
  <bar>Bar 2</bar>
</mydoc>

以下用于测试XML un / marshalling的golang代码(也在the go playground上):

package main

import "encoding/xml"
import "fmt"

const sampleXml = `
<mydoc>
  <foo>Foo</foo>
  <bar>Bar</bar>
  <foo>Another Foo</foo>
  <foo>Foo #3</foo>
  <bar>Bar 2</bar>
</mydoc>
`

type MyDoc struct {
  XMLName xml.Name `xml:"mydoc"`
  Items   []Item
}

type Item interface {
  IsItem()
}

type Foo struct {
  XMLName xml.Name `xml:"foo"`
  Name    string   `xml:",chardata"`
}

func (f Foo) IsItem() {}

type Bar struct {
  XMLName xml.Name `xml:"bar"`
  Nombre  string   `xml:",chardata"`
}

func (b Bar) IsItem() {}

func main() {
  doMarshal()
  doUnmarshal()
}

func doMarshal() {
  myDoc := MyDoc{
    Items: []Item{
      Foo{Name: "Foo"},
      Bar{Nombre: "Bar"},
      Foo{Name: "Another Foo"},
      Foo{Name: "Foo #3"},
      Bar{Nombre: "Bar 2"},
    },
  }
  bytes, err := xml.MarshalIndent(myDoc, "", "  ")
  if err != nil {
    panic(err)
  }
  // Prints an XML document just like "sampleXml" above.
  println(string(bytes))
}

func doUnmarshal() {
  myDoc := MyDoc{}
  err := xml.Unmarshal([]byte(sampleXml), &myDoc)
  if err != nil {
    panic(err)
  }
  // Fails to unmarshal the "Item" elements into their respective structs.
  fmt.Printf("ERR: %#v", myDoc)
}

您将看到doMarshal()生成我期望的确切XML文档;但是,doUnmarshal()无法反序列化&#34;项目&#34;元素分为各自的结构。我尝试了一些更改,但似乎没有任何内容可以让他们正确解组(为myDoc.Items创建存储空间,将&#34;项目&#34;的类型更改为[]*Item [和其他人] ,摆弄XML标签等。)

如何让xml.Unmarshal(...)反序列化不相关类型的元素列表?

1 个答案:

答案 0 :(得分:2)

正如其他评论所指出的,如果没有一些帮助,解码器无法处理接口字段。在容器上实现xml.Unmarshaller将使其按照您的要求执行(playground上的完整工作示例):

func (md *MyDoc) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    md.XMLName = start.Name
    // grab any other attrs

    // decode inner elements
    for {
        t, err := d.Token()
        if err != nil {
            return err
        }
        var i Item
        switch tt := t.(type) {
        case xml.StartElement:
            switch tt.Name.Local {
            case "foo":
                i = new(Foo) // the decoded item will be a *Foo, not Foo!
            case "bar":
                i = new(Bar)
                // default: ignored for brevity
            }
            // known child element found, decode it
            if i != nil {
                err = d.DecodeElement(i, &tt)
                if err != nil {
                    return err
                }
                md.Items = append(md.Items, i)
                i = nil
            }
        case xml.EndElement:
            if tt == start.End() {
                return nil
            }
        }

    }
    return nil
}

这只是@evanmcdonnal建议的实现。所有这一切都是根据下一个令牌的名称实例化正确的Item,然后用它调用d.DecodeElement()(即让xml解码器完成繁重的工作)。

请注意,unmarshalled Items是指针。如果你想要价值,你需要做更多的工作。为了正确处理错误或意外的输入数据,还需要进一步扩展。