You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:18:40