
本文详解如何在 go 中使用 `encoding/xml` 正确反序列化含重复子元素(如多个 `
在 Go 中解析 XML 时,一个常见误区是将包含多个同名子元素的容器(如 <Tmps><Tmp>...</Tmp><Tmp>...</Tmp></Tmps>)错误地建模为单个结构体字段,而非切片。这会导致 xml.Unmarshal 或 xml.Decoder.Decode 仅保留最后一个匹配项(正如示例中 Tmps.Tmp 只存入第 5 个温度点),而 <AOs> 和 <AIs> 完全未被解析——根本原因在于结构体字段类型与 XML 数据结构不匹配,且部分字段未导出或标签配置不当。
✅ 正确建模原则
- 重复元素 → 切片字段:每个 <Tmp> 是独立项,Tmps 应持有一个 []Tmp 切片,而非单个 Tmp。
- 字段必须导出(首字母大写):Go 的 xml 包只能访问导出字段(如 ID 而非 id),否则属性和内容均无法绑定。
- XML 标签名默认匹配字段名:若 XML 元素名为 <AO>,对应结构体字段应命名为 AO(大写),并用 xml:"AO" 显式声明更安全;若省略标签,包会按字段名自动匹配(但要求大小写完全一致)。
- 属性名需显式标注:使用 xml:"attrName,attr" 绑定属性,如 ID stringxml:"id,attr``。
✅ 修正后的结构体定义
type Clock struct {
Date string `xml:"Date"`
Time string `xml:"Time"`
Day string `xml:"Day"`
}
type Tmp struct {
ID string `xml:"id,attr"` // 属性:id="1"
Low string `xml:"lo,attr"` // 注意原 XML 是 "lo"/"hi",非 "low"/"high"
High string `xml:"hi,attr"`
Value string `xml:",chardata"` // 文本内容:283
}
type Tmps struct {
Tmp []Tmp `xml:"Tmp"` // ✅ 切片 + 显式标签,支持多个 <Tmp>
}
type AO struct {
ID string `xml:"id,attr"`
Val string `xml:",chardata"` // 字段名建议语义化(避免与类型名冲突)
}
type AOs struct {
AO []AO `xml:"AO"`
}
type AI struct {
ID string `xml:"id,attr"`
Low string `xml:"lo,attr"`
High string `xml:"hi,attr"`
Val string `xml:",chardata"`
}
type AIs struct {
AI []AI `xml:"AI"`
}
type WebbrickStatus struct {
XMLName xml.Name `xml:"WebbrickStatus"` // 可选:明确根元素
Ver string `xml:"Ver,attr"` // 解析版本属性
Error string `xml:"Error"`
Context string `xml:"Context"`
LoginState string `xml:"LoginState"`
DI string `xml:"DI"`
DO string `xml:"DO"`
Clock Clock `xml:"Clock"`
OWbus string `xml:"OWBus"` // 注意:XML 中为 <OWBus>,非 <OWbus>
Tmps Tmps `xml:"Tmps"`
AOS AOs `xml:"AOs"`
AIS AIs `xml:"AIs"`
}⚠️ 关键修正说明:Tmps.Tmp、AOs.AO、AIs.AI 均改为 []T/[]AO/[]AI 切片;所有字段首字母大写(ID, Low, Val 等),确保可导出;属性名严格对应 XML 实际值:lo / hi(非 low/high),OWBus(非 OWbus);xml:",chardata" 正确捕获元素文本内容(如 <Tmp>283</Tmp> 中的 283)。
✅ 完整解码流程(含编码转换)
func GetWBStatus() bool {
strMsg := `<?xml version="1.0" encoding="ISO-8859-1"?>
<WebbrickStatus Ver="6.1.614">
<!-- ... 同上完整 XML ... -->
</WebbrickStatus>`
msg := []byte(strMsg)
reader := bytes.NewReader(msg)
decoder := xml.NewDecoder(reader)
decoder.CharsetReader = charset.NewReader // 处理 ISO-8859-1 → UTF-8
var wbs WebbrickStatus
if err := decoder.Decode(&wbs); err != nil {
fmt.Printf("XML decode error: %v\n", err)
return false
}
fmt.Printf("Parsed %d Tmps, %d AOs, %d AIs\n",
len(wbs.Tmps.Tmp), len(wbs.AOS.AO), len(wbs.AIS.AI))
// 示例输出:Parsed 5 Tmps, 4 AOs, 4 AIs
for i, t := range wbs.Tmps.Tmp {
fmt.Printf("Tmp[%d]: ID=%s, Low=%s, High=%s, Value=%s\n",
i, t.ID, t.Low, t.High, t.Value)
}
return true
}? 总结与最佳实践
- 永远用切片承载重复元素:这是解决“只取最后一个”的最核心修复;
- 导出 + 标签双保险:字段大写 + xml:"..." 显式声明,避免隐式匹配失败;
- 校验 XML 实际结构:用浏览器或 xmllint 查看原始响应,确认大小写、属性名(lo vs low)、元素名(OWBus);
- Charset 转换需早于解码:decoder.CharsetReader 必须在 Decode() 前设置;
- 调试技巧:先用 xml.Unmarshal([]byte(xmlStr), &v) 简化测试,排除 Reader 层干扰。
遵循以上模式,即可稳健解析各类嵌套、重复、带属性的工业设备 XML 数据,无需第三方库,纯标准库高效可靠。


















