猿问

Golang xml.Unmarshal 接口类型

xml在 golang 中使用该包时,我在解组非同质类型列表时遇到了麻烦。考虑以下 XML 文档,其嵌套元素是非同类类型的列表:


<mydoc>

  <foo>Foo</foo>

  <bar>Bar</bar>

  <foo>Another Foo</foo>

  <foo>Foo #3</foo>

  <bar>Bar 2</bar>

</mydoc>

以及以下用于测试 XML 解组/编组的 golang 代码(也在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()无法将“Item”元素反序列化为它们各自的结构。我已经尝试了一些更改,但似乎没有什么能让它们正确地解组(创建存储myDoc.Items,将“项目”的类型更改为[]*Item[和其他],摆弄 XML 标签等)。


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


HUH函数
浏览 176回答 1
1回答

富国沪深

正如其他评论所指出的,解码器无法在没有帮助的情况下处理接口字段。xml.Unmarshaller在容器上实现将使它做你想做的事(操场上的完整工作示例):func (md *MyDoc) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {&nbsp; &nbsp; md.XMLName = start.Name&nbsp; &nbsp; // grab any other attrs&nbsp; &nbsp; // decode inner elements&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; t, err := d.Token()&nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; var i Item&nbsp; &nbsp; &nbsp; &nbsp; switch tt := t.(type) {&nbsp; &nbsp; &nbsp; &nbsp; case xml.StartElement:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch tt.Name.Local {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "foo":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i = new(Foo) // the decoded item will be a *Foo, not Foo!&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "bar":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i = new(Bar)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // default: ignored for brevity&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // known child element found, decode it&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if i != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; err = d.DecodeElement(i, &tt)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return err&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; md.Items = append(md.Items, i)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i = nil&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; case xml.EndElement:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if tt == start.End() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; return nil}这只是@evanmcdonnal 建议的实现。所有这些都是Item根据下一个令牌的名称实例化正确的,然后d.DecodeElement()用它调用(即让 xml 解码器完成繁重的工作)。请注意,未编组的Items是指针。如果你想要值,你需要做更多的工作。这还需要进一步扩展以正确处理错误或意外输入数据。
随时随地看视频慕课网APP

相关分类

Go
我要回答