2015-11-02 56 views
2

在golang中使用xml包我无法解编非同类类型的列表。考虑其嵌套元素都是非同质类型的列表下面的XML文档:Golang xml.Unmarshal接口类型

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

而下面golang代码(在the go playground也在这里)测试XML UN /编组:

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(...)反序列化不相关类型的元素列表?

+7

如果一个字段是一个接口,解码器无法告诉要使用的实际类型。我不记得这是否工作,但尝试建立一个初始化接口值的结构。 – JimB

+0

@JimB:啊,是的,这非常有道理 - 解码器不可能知道使用哪种类型。我想这是我面临的一般问题。也许我只需要直接使用解码器并将元素名称映射到已知类型。 – maerics

+0

也更一般:Go接口是关于行为(仅限方法,而不是属性),而不是数据。所以我认为这是对接口理念的误用。 – 0x434D53

回答

0

正如其他评论所指出的,解码器无法在没有帮助的情况下处理接口字段。在容器上实施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解码器完成繁重的工作)。请注意,未编组的Items是指针。如果你想要值,你需要做更多的工作。这还需要进一步扩展,以便正确处理错误或意外的输入数据。