Go语言解析含可变子类型的XML:Unmarshal实现方案咨询
Hey Dan, 针对你遇到的XML反序列化问题,我来给你梳理两个可行的方案,完全能覆盖sms和mms混排的场景,而且不用在多属性结构体上额外耗费不必要的精力!
方案一:自定义UnmarshalXML方法(推荐,逻辑更清晰)
这种方式可以直接把sms和mms分别解析到不同的切片里,后续处理不用做类型断言,非常直观。
首先定义你的sms和mms结构体,把对应的属性和XML标签写好就行(属性多也没关系,按实际字段定义,甚至可以用工具自动生成):
package main import ( "encoding/xml" "fmt" ) type Sms struct { XMLName xml.Name `xml:"sms"` ID string `xml:"id,attr"` Body string `xml:"body"` // 其他sms属性... } type Mms struct { XMLName xml.Name `xml:"mms"` ID string `xml:"id,attr"` Subject string `xml:"subject"` Attachments int `xml:"attachments,attr"` // 其他mms属性... } type Smses struct { SmsList []Sms MmsList []Mms } // 自定义UnmarshalXML方法,手动解析每个子节点 func (s *Smses) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { for { token, err := d.Token() if err != nil { return err } switch elem := token.(type) { case xml.StartElement: // 根据节点名判断是sms还是mms,分别解析 switch elem.Name.Local { case "sms": var sms Sms if err := d.DecodeElement(&sms, &elem); err != nil { return err } s.SmsList = append(s.SmsList, sms) case "mms": var mms Mms if err := d.DecodeElement(&mms, &elem); err != nil { return err } s.MmsList = append(s.MmsList, mms) } case xml.EndElement: // 遇到smses的结束标签就停止解析 if elem == start.End() { return nil } } } } // 测试用例 func main() { xmlData := []byte(` <smses> <sms id="1" body="Hello World"/> <mms id="2" subject="Test MMS" attachments="1"/> <sms id="3" body="Another SMS"/> </smses> `) var smses Smses err := xml.Unmarshal(xmlData, &smses) if err != nil { panic(err) } fmt.Println("SMS List:") for _, sms := range smses.SmsList { fmt.Printf("ID: %s, Body: %s\n", sms.ID, sms.Body) } fmt.Println("\nMMS List:") for _, mms := range smses.MmsList { fmt.Printf("ID: %s, Subject: %s, Attachments: %d\n", mms.ID, mms.Subject, mms.Attachments) } }
这个方案的优势是逻辑清晰,后续处理sms和mms不用做类型断言,直接操作对应的切片就行,完全覆盖两种节点类型的解析需求。
方案二:接口切片(更简洁,适合快速实现)
如果你想用接口切片的方式,也是完全可行的。只需要给sms和mms结构体加上XMLName标签,Go的xml包会自动根据节点名把它们解析成对应的类型,放到接口切片里。
示例代码:
package main import ( "encoding/xml" "fmt" ) type Sms struct { XMLName xml.Name `xml:"sms"` ID string `xml:"id,attr"` Body string `xml:"body"` } type Mms struct { XMLName xml.Name `xml:"mms"` ID string `xml:"id,attr"` Subject string `xml:"subject"` Attachments int `xml:"attachments,attr"` } type Smses struct { Messages []interface{} `xml:",any"` } func main() { xmlData := []byte(` <smses> <sms id="1" body="Hello World"/> <mms id="2" subject="Test MMS" attachments="1"/> <sms id="3" body="Another SMS"/> </smses> `) var smses Smses err := xml.Unmarshal(xmlData, &smses) if err != nil { panic(err) } for _, msg := range smses.Messages { switch item := msg.(type) { case Sms: fmt.Printf("SMS - ID: %s, Body: %s\n", item.ID, item.Body) case Mms: fmt.Printf("MMS - ID: %s, Subject: %s, Attachments: %d\n", item.ID, item.Subject, item.Attachments) } } }
这种方式代码更简洁,但后续需要用类型断言来区分sms和mms,适合快速实现的场景。
总结
两种方案都完全可行,不用担心覆盖不了两种节点类型。对于属性多的结构体,你只需要按XML结构定义好结构体字段和标签就行,甚至可以用在线工具自动生成结构体代码,节省手动编写的时间。
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

