通八洲科技

如何在 Go 中正确反序列化多个同级 XML 元素(而非单个根节点)

日期:2026-01-01 00:00 / 作者:聖光之護

go 的 `xml.unmarshal` 要求输入 xml 有且仅有一个根元素;若原始数据是多个并列的 `` 标签(无外层包裹),直接调用 `unmarshal` 会只解析第一个并静默忽略其余——需改用 `xml.decoder` 循环调用 `decode` 才能完整提取全部项。

在 Go 中处理 VMware vSphere 等系统返回的 XML 数据时,一个常见陷阱是:summary.hardware.otherIdentifyingInfo 字段的值并非标准 XML 文档(即带单一根节点),而是多个同级 元素的拼接字符串。这种结构不符合 xml.Unmarshal 的设计前提——它期望整个字节流对应一个 XML 文档(single root),因此默认只会解析第一个元素,后续内容被丢弃。

✅ 正确解法是使用 xml.Decoder,它支持流式解析,可多次调用 Decode() 方法,每次读取并解析一个独立的 XML 元素:

import (
    "bytes"
    "encoding/xml"
    "io"
)

type HostSystemIdentificationInfo struct { // 注意:此处改为普通 struct,非切片
    IdentifierValue string `xml:"identifierValue"`
    IdentifierType  struct {
        Label   string `xml:"label"`
        Summary string `xml:"summary"`
        Key     string `xml:"key"`
    } `xml:"identifierType"`
}

// 解析多个并列的 HostSystemIdentificationInfo
func parseIdentificationInfos(xmlData string) ([]HostSystemIdentificationInfo, error) {
    var results []HostSystemIdentificationInfo
    decoder := xml.NewDecoder(bytes.NewBufferString(xmlData))

    for {
        var item HostSystemIdentificationInfo
        err := decoder.Decode(&item)
        if err == io.EOF {
            break // 所有元素已读完
        }
        if err != nil {
            return nil, fmt.Errorf("failed to decode XML item: %w", err)
        }
        results = append(results, item)
    }

    return results, nil
}

? 关键要点:

在你的 vSphere 客户端逻辑中,只需将原 xml.Unmarshal 替换为上述 parseIdentificationInfos 调用:

if p.Name == "summary.hardware.otherIdentifyingInfo" {
    infos, err := parseIdentificationInfos(p.Val.Inner)
    if err != nil {
        return fmt.Errorf("failed to parse host identification info: %w", err)
    }
    fmt.Printf("Parsed %d identification entries\n", len(infos))
    for _, info := range infos {
        fmt.Printf("- Value: %q, Type: %q (Key: %q)\n", 
            info.IdentifierValue, 
            info.IdentifierType.Label, 
            info.IdentifierType.Key)
    }
}

✅ 总结:当面对“无根多节点 XML 字符串”时,xml.Decoder + 循环 Decode() 是 Go 标准库提供的标准、可靠且内存友好的解决方案。避免强行添加虚拟根节点(如 ...)再解析——这不仅增加字符串操作开销,还可能因转义或命名空间问题引入新 bug。