
本文详解如何在 go 中使用 `encoding/xml` 正确解析含重复子元素(如多个 `
在 Go 中解析 XML 时,一个常见却容易被忽视的关键点是:当 XML 中某标签重复出现(如 <Tmp>...</Tmp> 出现多次),其对应 Go 结构体字段必须声明为切片([]T)类型,而非单个结构体实例。否则,encoding/xml 包仅会保留最后一次解析的结果——这正是原代码中 Tmps 只保留最后一个温度值、AOs/AIs 完全未映射的根本原因。
✅ 正确的结构体建模方式
首先修正聚合容器类型(Tmps、AOs、AIs),使其内部字段为切片:
type Tmps struct {
Tmp []Tmp `xml:"Tmp"` // 注意:xml tag 显式指定为 "Tmp",因实际 XML 中子元素名为 <Tmp>
}
type Tmp struct {
ID string `xml:"id,attr"`
Low string `xml:"lo,attr"` // 原 XML 属性名为 "lo",非 "low"
High string `xml:"hi,attr"` // 原 XML 属性名为 "hi",非 "high"
Value string `xml:",chardata"`
}
type AOs struct {
AO []AO `xml:"AO"`
}
type AO struct {
ID string `xml:"id,attr"`
Val string `xml:",chardata"`
}
type AIs struct {
AI []AI `xml:"AI"`
}
type AI struct {
ID string `xml:"id,attr"`
Low string `xml:"lo,attr"`
High string `xml:"hi,attr"`
Val string `xml:",chardata"`
}? 关键修正说明:Tmps.Tmp、AOs.AO、AIs.AI 必须为 []T 切片类型,才能接收多个同名节点;属性名需严格匹配 XML 实际内容:<Tmp id="1" lo="-800" hi="384"> → 对应 id, lo, hi,而非 low/high;字段名建议采用导出(首字母大写),否则 xml 包无法反射访问(原代码中 low/high 等为小写,导致零值);xml:"..." 标签中若结构体字段名与 XML 元素名一致(如 AO 字段对应 <AO>),可省略;但为清晰起见,显式标注更稳妥。
? 主结构体同步更新
WebbrickStatus 中的嵌入字段也需同步调整,并确保所有字段导出:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
type WebbrickStatus struct {
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"`
}⚠️ 注意大小写:XML 中为 <OWBus>,而原结构体字段 OWbus 的 xml tag 缺失或不匹配,将导致该字段为空。
?️ 完整可运行示例(精简版)
package main
import (
"bytes"
"fmt"
"encoding/xml"
)
// ... [上述已修正的结构体定义] ...
func main() {
xmlData := `<?xml version="1.0" encoding="ISO-8859-1"?>
<WebbrickStatus Ver="6.1.614">
<Error>0</Error>
<Context>2</Context>
<LoginState>3</LoginState>
<DI>0</DI>
<DO>0</DO>
<Clock><Date>0/0/0</Date><Time>12:54:52</Time><Day>3</Day></Clock>
<OWBus>1</OWBus>
<Tmps>
<Tmp id="1" lo="-800" hi="384">283</Tmp>
<Tmp id="2" lo="-800" hi="1600">0</Tmp>
</Tmps>
<AOs>
<AO id="0">0</AO>
<AO id="1">0</AO>
</AOs>
<AIs>
<AI id="0" lo="0" hi="100">1</AI>
</AIs>
</WebbrickStatus>`
var wbs WebbrickStatus
err := xml.Unmarshal([]byte(xmlData), &wbs)
if err != nil {
panic(err)
}
fmt.Printf("Tmps count: %d\n", len(wbs.Tmps.Tmp))
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)
}
}✅ 总结与最佳实践
- 切片是重复元素的唯一正确载体:永远不要用单个结构体接收多个同名 XML 节点;
- 属性名、元素名、字段名三者需精确对齐,区分大小写与缩写(lo ≠ low);
- 所有待解析字段必须导出(首字母大写),否则 xml 包无法访问;
- 使用 xml:",chardata" 获取文本内容,xml:"name,attr" 绑定属性;
- 若需处理编码转换(如 ISO-8859-1),优先考虑 golang.org/x/text/encoding 替代已归档的 go-charset,以保证长期维护性;
- 开发时建议配合 Go Playground 示例 快速验证结构体映射逻辑。
遵循以上原则,即可稳健解析各类嵌套、重复、带属性的工业设备 XML 输出。

















