2015-11-02 34 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是指針。如果你想要值,你需要做更多的工作。這還需要進一步擴展,以便正確處理錯誤或意外的輸入數據。