encoding/xml - XML 编解码
概述
encoding/xml 包提供了 XML(Extensible Markup Language)数据的编码和解码功能。
XML 是什么:
- 📦 可扩展标记语言:用于存储和传输数据的标记语言
- 🔧 自描述格式:数据包含结构信息
- 📋 人类可读:文本格式,易于阅读和理解
- 🛠️ 跨平台支持:广泛用于 Web 服务、配置文件
主要用途:
- 🌐 Web Services:SOAP API、XML-RPC
- 📧 数据交换:系统间的数据传输
- 🔐 配置文件:应用程序配置存储
- 📊 文档格式:RSS、Atom、SVG 等
- 🖼️ 数据持久化:结构化数据存储
- 🔑 数字签名:XML Signature、XML Encryption
重要说明:
- ⚠️ 严格语法:XML 语法要求严格(标签必须闭合)
- ⚠️ 命名空间:支持 XML Namespaces
- ⚠️ 属性支持:支持 XML 属性
- ⚠️ 字符编码:支持多种字符编码
- ✅ 标准库支持:Go 标准库提供完整支持
- ✅ 流式处理:支持 Token 流式解析
- ✅ 结构体标签:使用 struct tag 自定义映射
XML 示例:
<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>John Doe</name>
<age>30</age>
<email>john@example.com</email>
<address city="New York" zip="10001">
<street>123 Main St</street>
</address>
</person>
XML 基础
XML 语法规则
基本规则:
- 必须有根元素
- 标签必须闭合
- 标签区分大小写
- 属性值必须用引号包围
- 特殊字符需要转义
特殊字符转义:
| 字符 | 转义 |
|---|---|
< | < |
> | > |
& | & |
" | " |
' | ' |
XML vs JSON
| 特性 | XML | JSON |
|---|---|---|
| 大小 | 较大 | 较小 |
| 可读性 | 好 | 好 |
| 元数据 | 支持属性 | 不支持 |
| 命名空间 | 支持 | 不支持 |
| 数组 | 无原生支持 | 原生支持 |
| 解析复杂度 | 较高 | 较低 |
核心类型
1. Name - XML 名称
type Name struct {
Space, Local string
}
功能:表示 XML 元素或属性的名称。
字段:
Space:命名空间(可选)Local:本地名称
示例:
name := xml.Name{
Space: "http://example.com/ns",
Local: "person",
}
2. Attr - XML 属性
type Attr struct {
Name Name
Value string
}
功能:表示 XML 元素的属性。
示例:
<person id="123" active="true">
对应:
[]xml.Attr{
{Name: xml.Name{Local: "id"}, Value: "123"},
{Name: xml.Name{Local: "active"}, Value: "true"},
}
3. StartElement - 开始标签
type StartElement struct {
Name Name
Attr []Attr
}
功能:表示 XML 开始标签(如 <person>)。
示例:
<person id="1">
对应:
xml.StartElement{
Name: xml.Name{Local: "person"},
Attr: []xml.Attr{
{Name: xml.Name{Local: "id"}, Value: "1"},
},
}
4. EndElement - 结束标签
type EndElement struct {
Name Name
}
功能:表示 XML 结束标签(如 </person>)。
5. CharData - 字符数据
type CharData []byte
功能:表示 XML 元素之间的文本内容。
示例:
<name>John Doe</name>
John Doe 就是 CharData。
6. Comment - 注释
type Comment []byte
功能:表示 XML 注释。
示例:
<!-- 这是一个注释 -->
7. ProcInst - 处理指令
type ProcInst struct {
Target string
Inst []byte
}
功能:表示 XML 处理指令。
示例:
<?xml version="1.0" encoding="UTF-8"?>
8. Directive - XML 指令
type Directive []byte
功能:表示 XML DOCTYPE 声明。
示例:
<!DOCTYPE html>
9. Token - XML Token 接口
type Token interface{}
功能:表示 XML Token,可以是 StartElement、EndElement、CharData 等。
10. Decoder - XML 解码器
type Decoder struct {
// 包含过滤或未导出的字段
}
功能:流式解析 XML。
创建方法:
func NewDecoder(r io.Reader) *Decoder
主要方法:
// 读取下一个 Token
func (d *Decoder) Token() (t Token, err error)
// 解码到结构体
func (d *Decoder) Decode(v interface{}) error
// 解码到指定类型
func (d *Decoder) DecodeElement(v interface{}, start *StartElement) error
11. Encoder - XML 编码器
type Encoder struct {
// 包含过滤或未导出的字段
}
功能:流式编码 XML。
创建方法:
func NewEncoder(w io.Writer) *Encoder
主要方法:
// 编码 Token
func (e *Encoder) Encode(t Token) error
// 编码结构体
func (e *Encoder) Encode(v interface{}) error
// 编码元素
func (e *Encoder) EncodeElement(v interface{}, start StartElement) error
// 刷新缓冲区
func (e *Encoder) Flush() error
Struct Tag 详解
基本语法
type Struct struct {
Field Type `xml:"name,flags"`
}
常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| 元素名 | 自定义元素名 | xml:"person" |
| 属性 | 映射为属性 | xml:"id,attr" |
| 字符数据 | 映射为文本 | xml:",chardata" |
| 注释 | 映射为注释 | xml:",comment" |
| 内联 | 扁平化嵌套 | xml:",any" |
| 通配符 | 匹配任意元素 | xml:",any" |
| 命名空间 | 指定命名空间 | xml:"ns:person" |
| - | 忽略字段 | xml:"-" |
元素名自定义
type Person struct {
Name string `xml:"name"` // 自定义元素名
Age int `xml:"age"`
}
对应 XML:
<person>
<name>John</name>
<age>30</age>
</person>
属性映射
type Person struct {
ID int `xml:"id,attr"` // 属性
Name string `xml:"name"` // 元素
}
对应 XML:
<person id="123">
<name>John</name>
</person>
字符数据
type HTML struct {
Content string `xml:",chardata"`
}
对应 XML:
<html>Some text content</html>
内联元素
type Company struct {
Name string `xml:"name"`
People []Person `xml:",any"` // 内联任意元素
}
嵌套结构
type Address struct {
City string `xml:"city"`
State string `xml:"state"`
}
type Person struct {
Name string `xml:"name"`
Address Address `xml:"address"`
}
对应 XML:
<person>
<name>John</name>
<address>
<city>New York</city>
<state>NY</state>
</address>
</person>
切片和数组
type People struct {
Persons []Person `xml:"person"`
}
对应 XML:
<people>
<person>
<name>John</name>
</person>
<person>
<name>Jane</name>
</person>
</people>
指针
type Document struct {
Title string `xml:"title"`
Author *string `xml:"author"` // 指针,可为 nil
}
核心函数
1. Marshal - 编码为 XML
func Marshal(v interface{}) ([]byte, error)
功能:将 Go 值编码为 XML 字节切片。
示例:
type Person struct {
Name string `xml:"name"`
Age int `xml:"age"`
}
person := Person{Name: "John", Age: 30}
data, err := xml.Marshal(person)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// 输出:<Person><name>John</name><age>30</age></Person>
2. MarshalIndent - 格式化编码
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
功能:将 Go 值编码为格式化的 XML(带缩进)。
示例:
data, err := xml.MarshalIndent(person, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
/*
输出:
<Person>
<name>John</name>
<age>30</age>
</Person>
*/
3. Unmarshal - 从 XML 解码
func Unmarshal(data []byte, v interface{}) error
功能:将 XML 数据解码到 Go 值。
示例:
var person Person
err := xml.Unmarshal(data, &person)
if err != nil {
log.Fatal(err)
}
4. NewDecoder - 创建解码器
func NewDecoder(r io.Reader) *Decoder
功能:创建流式 XML 解码器。
5. NewEncoder - 创建编码器
func NewEncoder(w io.Writer) *Encoder
功能:创建流式 XML 编码器。
完整示例
示例 1:基本编解码
package main
import (
"encoding/xml"
"fmt"
"log"
)
// Person 人员结构
type Person struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
Age int `xml:"age"`
Email string `xml:"email"`
}
func main() {
fmt.Println("=== XML 基本编解码 ===\n")
// 1. 创建数据
person := Person{
Name: "John Doe",
Age: 30,
Email: "john@example.com",
}
fmt.Printf("原始数据:\n")
fmt.Printf(" Name: %s\n", person.Name)
fmt.Printf(" Age: %d\n", person.Age)
fmt.Printf(" Email: %s\n\n", person.Email)
// 2. 编码为 XML
fmt.Println("2. 编码为 XML:")
data, err := xml.Marshal(person)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 紧凑格式:%s\n\n", string(data))
// 格式化输出
indentData, err := xml.MarshalIndent(person, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 格式化格式:\n%s\n\n", string(indentData))
// 3. 从 XML 解码
fmt.Println("3. 从 XML 解码:")
var decoded Person
err = xml.Unmarshal(data, &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 解码结果:\n")
fmt.Printf(" Name: %s\n", decoded.Name)
fmt.Printf(" Age: %d\n", decoded.Age)
fmt.Printf(" Email: %s\n\n", decoded.Email)
// 4. 验证
fmt.Printf("验证:%v\n", person == decoded)
}
输出:
=== XML 基本编解码 ===
原始数据:
Name: John Doe
Age: 30
Email: john@example.com
2. 编码为 XML:
紧凑格式:<Person><name>John Doe</name><age>30</age><email>john@example.com</email></Person>
格式化格式:
<Person>
<name>John Doe</name>
<age>30</age>
<email>john@example.com</email>
</Person>
3. 从 XML 解码:
解码结果:
Name: John Doe
Age: 30
Email: john@example.com
验证:true
示例 2:属性处理
package main
import (
"encoding/xml"
"fmt"
"log"
)
// Product 产品(包含属性)
type Product struct {
XMLName xml.Name `xml:"product"`
ID int `xml:"id,attr"`
Category string `xml:"category,attr"`
Name string `xml:"name"`
Price float64 `xml:"price"`
InStock bool `xml:"in_stock,attr"`
}
func main() {
fmt.Println("=== XML 属性处理 ===\n")
// 1. 创建数据
product := Product{
ID: 123,
Category: "Electronics",
Name: "Laptop",
Price: 999.99,
InStock: true,
}
// 2. 编码
fmt.Println("2. 编码为 XML:")
data, err := xml.MarshalIndent(product, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n\n", string(data))
// 3. 解码
fmt.Println("3. 从 XML 解码:")
xmlStr := `<product id="456" category="Books" in_stock="true">
<name>Go Programming</name>
<price>49.99</price>
</product>`
var decoded Product
err = xml.Unmarshal([]byte(xmlStr), &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ID: %d\n", decoded.ID)
fmt.Printf(" Category: %s\n", decoded.Category)
fmt.Printf(" Name: %s\n", decoded.Name)
fmt.Printf(" Price: %.2f\n", decoded.Price)
fmt.Printf(" InStock: %v\n", decoded.InStock)
}
输出:
=== XML 属性处理 ===
2. 编码为 XML:
<Product id="123" category="Electronics" in_stock="true">
<name>Laptop</name>
<price>999.99</price>
</Product>
3. 从 XML 解码:
ID: 456
Category: Books
Name: Go Programming
Price: 49.99
InStock: true
示例 3:嵌套结构
package main
import (
"encoding/xml"
"fmt"
"log"
)
// Address 地址
type Address struct {
Street string `xml:"street"`
City string `xml:"city"`
State string `xml:"state"`
ZipCode string `xml:"zip_code"`
Country string `xml:"country"`
}
// Contact 联系方式
type Contact struct {
Type string `xml:"type,attr"`
Value string `xml:",chardata"`
}
// Person 人员(嵌套结构)
type Person struct {
XMLName xml.Name `xml:"person"`
ID int `xml:"id,attr"`
Name string `xml:"name"`
Age int `xml:"age"`
Address Address `xml:"address"`
Contacts []Contact `xml:"contact"`
}
func main() {
fmt.Println("=== XML 嵌套结构 ===\n")
// 1. 创建数据
person := Person{
ID: 1,
Name: "John Doe",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "New York",
State: "NY",
ZipCode: "10001",
Country: "USA",
},
Contacts: []Contact{
{Type: "email", Value: "john@example.com"},
{Type: "phone", Value: "+1-555-1234"},
},
}
// 2. 编码
fmt.Println("2. 编码为 XML:")
data, err := xml.MarshalIndent(person, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n\n", string(data))
// 3. 解码
fmt.Println("3. 从 XML 解码:")
var decoded Person
err = xml.Unmarshal(data, &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 姓名:%s\n", decoded.Name)
fmt.Printf(" 年龄:%d\n", decoded.Age)
fmt.Printf(" 城市:%s\n", decoded.Address.City)
fmt.Printf(" 联系方式:%d 个\n", len(decoded.Contacts))
for i, contact := range decoded.Contacts {
fmt.Printf(" 联系 %d: [%s] %s\n", i+1, contact.Type, contact.Value)
}
}
输出:
=== XML 嵌套结构 ===
2. 编码为 XML:
<Person id="1">
<name>John Doe</name>
<age>30</age>
<address>
<street>123 Main St</street>
<city>New York</city>
<state>NY</state>
<zip_code>10001</zip_code>
<country>USA</country>
</address>
<contact type="email">john@example.com</contact>
<contact type="phone">+1-555-1234</contact>
</Person>
3. 从 XML 解码:
姓名:John Doe
年龄:30
城市:New York
联系方式:2 个
联系 1: [email] john@example.com
联系 2: [phone] +1-555-1234
示例 4:切片和数组
package main
import (
"encoding/xml"
"fmt"
"log"
)
// Item 项目
type Item struct {
ID int `xml:"id,attr"`
Name string `xml:"name"`
Value int `xml:"value"`
}
// Inventory 库存
type Inventory struct {
XMLName xml.Name `xml:"inventory"`
Items []Item `xml:"item"`
}
func main() {
fmt.Println("=== XML 切片和数组 ===\n")
// 1. 创建数据
inventory := Inventory{
Items: []Item{
{ID: 1, Name: "Laptop", Value: 10},
{ID: 2, Name: "Mouse", Value: 50},
{ID: 3, Name: "Keyboard", Value: 30},
},
}
// 2. 编码
fmt.Println("2. 编码为 XML:")
data, err := xml.MarshalIndent(inventory, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n\n", string(data))
// 3. 解码
fmt.Println("3. 从 XML 解码:")
xmlStr := `<inventory>
<item id="4">
<name>Monitor</name>
<value>20</value>
</item>
<item id="5">
<name>Webcam</name>
<value>15</value>
</item>
</inventory>`
var decoded Inventory
err = xml.Unmarshal([]byte(xmlStr), &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 项目数:%d\n", len(decoded.Items))
for _, item := range decoded.Items {
fmt.Printf(" [%d] %s - 数量:%d\n", item.ID, item.Name, item.Value)
}
}
输出:
=== XML 切片和数组 ===
2. 编码为 XML:
<Inventory>
<Item id="1">
<name>Laptop</name>
<value>10</value>
</Item>
<Item id="2">
<name>Mouse</name>
<value>50</value>
</Item>
<Item id="3">
<name>Keyboard</name>
<value>30</value>
</Item>
</Inventory>
3. 从 XML 解码:
项目数:2
[4] Monitor - 数量:20
[5] Webcam - 数量:15
示例 5:流式解析
package main
import (
"encoding/xml"
"fmt"
"io"
"log"
"strings"
)
func main() {
fmt.Println("=== XML 流式解析 ===\n")
// XML 数据
xmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<!-- 产品目录 -->
<product id="1">
<name>Laptop</name>
<price>999.99</price>
</product>
<product id="2">
<name>Mouse</name>
<price>29.99</price>
</product>
</catalog>`
// 1. 创建解码器
fmt.Println("1. 创建流式解码器:")
decoder := xml.NewDecoder(strings.NewReader(xmlStr))
// 2. 遍历所有 Token
fmt.Println("2. 遍历 Token:")
for {
token, err := decoder.Token()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
switch t := token.(type) {
case xml.ProcInst:
fmt.Printf(" 处理指令:%s %s\n", t.Target, string(t.Inst))
case xml.Comment:
fmt.Printf(" 注释:%s\n", string(t))
case xml.StartElement:
fmt.Printf(" 开始标签:%s", t.Name.Local)
if len(t.Attr) > 0 {
attrs := make([]string, len(t.Attr))
for i, attr := range t.Attr {
attrs[i] = fmt.Sprintf("%s=%s", attr.Name.Local, attr.Value)
}
fmt.Printf(" (%s)", strings.Join(attrs, ", "))
}
fmt.Println()
case xml.EndElement:
fmt.Printf(" 结束标签:%s\n", t.Name.Local)
case xml.CharData:
text := strings.TrimSpace(string(t))
if text != "" {
fmt.Printf(" 文本:%s\n", text)
}
}
}
// 3. 使用 Decode 方法
fmt.Println("\n3. 使用 Decode 方法:")
type Product struct {
ID int `xml:"id,attr"`
Name string `xml:"name"`
Price float64 `xml:"price"`
}
type Catalog struct {
Products []Product `xml:"product"`
}
decoder = xml.NewDecoder(strings.NewReader(xmlStr))
var catalog Catalog
for {
token, err := decoder.Token()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if startElem, ok := token.(xml.StartElement); ok {
if startElem.Name.Local == "product" {
var product Product
err := decoder.DecodeElement(&product, &startElem)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" 产品:%d - %s ($%.2f)\n",
product.ID, product.Name, product.Price)
}
}
}
}
输出:
=== XML 流式解析 ===
1. 创建流式解码器:
2. 遍历 Token:
处理指令:xml version="1.0" encoding="UTF-8"
开始标签:catalog
注释: 产品目录
开始标签:product (id=1)
开始标签:name
文本:Laptop
结束标签:name
开始标签:price
文本:999.99
结束标签:price
结束标签:product
开始标签:product (id=2)
开始标签:name
文本:Mouse
结束标签:name
开始标签:price
文本:29.99
结束标签:price
结束标签:product
结束标签:catalog
3. 使用 Decode 方法:
产品:1 - Laptop ($999.99)
产品:2 - Mouse ($29.99)
示例 6:命名空间处理
package main
import (
"encoding/xml"
"fmt"
"log"
)
// Person 带命名空间
type Person struct {
XMLName xml.Name `xml:"http://example.com/ns person"`
Name string `xml:"http://example.com/ns name"`
Age int `xml:"http://example.com/ns age"`
}
func main() {
fmt.Println("=== XML 命名空间处理 ===\n")
// 1. 编码
fmt.Println("1. 编码带命名空间的 XML:")
person := Person{
Name: "John Doe",
Age: 30,
}
data, err := xml.MarshalIndent(person, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n\n", string(data))
// 2. 解码
fmt.Println("2. 解码带命名空间的 XML:")
xmlStr := `<?xml version="1.0" encoding="UTF-8"?>
<ns:person xmlns:ns="http://example.com/ns">
<ns:name>Jane Smith</ns:name>
<ns:age>25</ns:age>
</ns:person>`
var decoded Person
err = xml.Unmarshal([]byte(xmlStr), &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" Name: %s\n", decoded.Name)
fmt.Printf(" Age: %d\n", decoded.Age)
}
输出:
=== XML 命名空间处理 ===
1. 编码带命名空间的 XML:
<person xmlns="http://example.com/ns">
<name>John Doe</name>
<age>30</age>
</person>
2. 解码带命名空间的 XML:
Name: Jane Smith
Age: 25
示例 7:自定义编解码
package main
import (
"encoding/xml"
"fmt"
"log"
"strconv"
"strings"
)
// CustomInt 自定义整数类型
type CustomInt int
// MarshalXML 自定义编码
func (c CustomInt) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
// 编码为字符串
return e.EncodeElement(strconv.Itoa(int(c)), start)
}
// UnmarshalXML 自定义解码
func (c *CustomInt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
err := d.DecodeElement(&s, &start)
if err != nil {
return err
}
// 从字符串解析
val, err := strconv.Atoi(strings.TrimSpace(s))
if err != nil {
return err
}
*c = CustomInt(val)
return nil
}
// Data 包含自定义类型
type Data struct {
XMLName xml.Name `xml:"data"`
ID CustomInt `xml:"id"`
Name string `xml:"name"`
Value int `xml:"value"`
}
func main() {
fmt.Println("=== XML 自定义编解码 ===\n")
// 1. 创建数据
data := Data{
ID: 123,
Name: "Test",
Value: 456,
}
// 2. 编码
fmt.Println("2. 编码:")
xmlData, err := xml.MarshalIndent(data, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n\n", string(xmlData))
// 3. 解码
fmt.Println("3. 解码:")
xmlInput := `<data>
<id>789</id>
<name>Custom</name>
<value>999</value>
</data>`
var decoded Data
err = xml.Unmarshal([]byte(xmlInput), &decoded)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ID: %d (类型:%T)\n", decoded.ID, decoded.ID)
fmt.Printf(" Name: %s\n", decoded.Name)
fmt.Printf(" Value: %d\n", decoded.Value)
}
输出:
=== XML 自定义编解码 ===
2. 编码:
<Data>
<id>123</id>
<name>Test</name>
<value>456</value>
</Data>
3. 解码:
ID: 789 (类型:main.CustomInt)
Name: Custom
Value: 999
示例 8:错误处理
package main
import (
"encoding/xml"
"fmt"
)
// Person 人员
type Person struct {
Name string `xml:"name"`
Age int `xml:"age"`
}
func main() {
fmt.Println("=== XML 错误处理 ===\n")
// 1. 无效 XML 语法
fmt.Println("1. 无效 XML 语法:")
invalidXMLs := []struct {
xml string
desc string
}{
{`<person><name>John</name>`, "缺少结束标签"},
{`<person><name>John</name></person`, "缺少 >"},
{`<person><name>John</name></PERSON>`, "标签大小写不匹配"},
{`<person name=John>`, "属性值未加引号"},
{`<person><name>John & Jane</name></person>`, "未转义的 &"},
}
for i, tc := range invalidXMLs {
var person Person
err := xml.Unmarshal([]byte(tc.xml), &person)
fmt.Printf(" 测试 %d (%s):\n", i+1, tc.desc)
if err != nil {
fmt.Printf(" ✗ 错误:%v\n\n", err)
} else {
fmt.Printf(" ? 意外成功\n\n")
}
}
// 2. 类型不匹配
fmt.Println("2. 类型不匹配:")
typeMismatch := `<person><name>John</name><age>not_a_number</age></person>`
var person Person
err := xml.Unmarshal([]byte(typeMismatch), &person)
fmt.Printf(" XML: %s\n", typeMismatch)
if err != nil {
fmt.Printf(" ✗ 错误:%v\n\n", err)
}
// 3. 字段缺失和多余
fmt.Println("3. 字段缺失和多余:")
// 字段缺失
missingField := `<person><name>John</name></person>`
var person2 Person
err = xml.Unmarshal([]byte(missingField), &person2)
fmt.Printf(" 字段缺失:\n")
fmt.Printf(" XML: %s\n", missingField)
if err != nil {
fmt.Printf(" ✗ 错误:%v\n", err)
} else {
fmt.Printf(" ✓ 成功(缺失字段为零值)\n")
fmt.Printf(" 结果:%+v\n\n", person2)
}
// 字段多余
extraField := `<person><name>John</name><age>30</age><email>john@example.com</email></person>`
var person3 Person
err = xml.Unmarshal([]byte(extraField), &person3)
fmt.Printf(" 字段多余:\n")
fmt.Printf(" XML: %s\n", extraField)
if err != nil {
fmt.Printf(" ✗ 错误:%v\n", err)
} else {
fmt.Printf(" ✓ 成功(多余字段被忽略)\n")
fmt.Printf(" 结果:%+v\n\n", person3)
}
// 4. 空 XML
fmt.Println("4. 空 XML:")
emptyXML := ``
var person4 Person
err = xml.Unmarshal([]byte(emptyXML), &person4)
fmt.Printf(" 空数据:\n")
if err != nil {
fmt.Printf(" ✗ 错误:%v\n", err)
}
}
输出:
=== XML 错误处理 ===
1. 无效 XML 语法:
测试 1 (缺少结束标签):
✗ 错误:XML syntax error on line 1: unexpected EOF
测试 2 (缺少 >):
✗ 错误:XML syntax error on line 1: expected '>' in tag
测试 3 (标签大小写不匹配):
✗ 错误:XML syntax error on line 1: element <name> closed by </PERSON>
测试 4 (属性值未加引号):
✗ 错误:XML syntax error on line 1: expected "=" after attribute name
测试 5 (未转义的 &):
✗ 错误:XML syntax error on line 1: invalid character entity & (no semicolon)
2. 类型不匹配:
XML: <person><name>John</name><age>not_a_number</age></person>
✗ 错误:strconv.ParseInt: parsing "not_a_number": invalid syntax
3. 字段缺失和多余:
字段缺失:
XML: <person><name>John</name></person>
✓ 成功(缺失字段为零值)
结果:{Name:John Age:0}
字段多余:
XML: <person><name>John</name><age>30</age><email>john@example.com</email></person>
✓ 成功(多余字段被忽略)
结果:{Name:John Age:30}
4. 空 XML:
空数据:
最佳实践
✅ 推荐做法
-
总是检查错误
// ✅ 推荐 data, err := xml.Marshal(v) if err != nil { return err } err = xml.Unmarshal(data, &v) if err != nil { return err } -
使用 struct tag 自定义元素名
// ✅ 推荐 type Person struct { Name string `xml:"name"` Age int `xml:"age"` } // ❌ 不推荐 type Person struct { Name string // 使用默认字段名 Age int } -
流式处理大文件
// ✅ 推荐:大文件 decoder := xml.NewDecoder(file) for { token, err := decoder.Token() if err == io.EOF { break } // 处理 token } -
使用指针处理可选元素
// ✅ 推荐 type Document struct { Title string `xml:"title"` Author *string `xml:"author"` // 可为 nil } -
处理命名空间
// ✅ 推荐:明确命名空间 type Person struct { XMLName xml.Name `xml:"http://example.com/ns person"` Name string `xml:"http://example.com/ns name"` }
❌ 不安全做法
-
不要忽略错误
// ❌ 错误 xml.Unmarshal(data, &v) // ✅ 正确 if err := xml.Unmarshal(data, &v); err != nil { return err } -
不要信任输入数据
// ❌ 错误 var v MyStruct xml.Unmarshal(input, &v) // 未验证 // ✅ 正确 if err := xml.Unmarshal(input, &v); err != nil { return err } // 验证 v 的字段 -
不要混用格式
// ❌ 错误 // 在同一文档中混用不同风格 // ✅ 正确 // 保持一致的命名和结构
性能优化
1. 使用 MarshalIndent 代替手动格式化
// ✅ 推荐
data, _ := xml.MarshalIndent(v, "", " ")
2. 预分配切片
// ✅ 推荐
items := make([]Item, 0, expectedCount)
xml.Unmarshal(data, &items)
3. 重用 Encoder/Decoder
// ✅ 推荐:重用
encoder := xml.NewEncoder(buf)
for _, item := range items {
encoder.Encode(item)
}
总结
核心类型
| 类型 | 用途 | 说明 |
|---|---|---|
| Name | XML 名称 | Space + Local |
| Attr | 属性 | Name + Value |
| StartElement | 开始标签 | Name + Attr |
| EndElement | 结束标签 | Name |
| CharData | 文本内容 | []byte |
| Decoder | 解码器 | 流式解析 |
| Encoder | 编码器 | 流式编码 |
核心函数
| 函数 | 用途 | 返回值 |
|---|---|---|
| Marshal | 编码为 XML | []byte, error |
| MarshalIndent | 格式化编码 | []byte, error |
| Unmarshal | 从 XML 解码 | error |
| NewDecoder | 创建解码器 | *Decoder |
| NewEncoder | 创建编码器 | *Encoder |
Struct Tag 选项
| 选项 | 说明 | 示例 |
|---|---|---|
| 元素名 | 自定义元素名 | xml:"name" |
| attr | 映射为属性 | xml:"id,attr" |
| chardata | 映射为文本 | xml:",chardata" |
| comment | 映射为注释 | xml:",comment" |
| any | 通配符 | xml:",any" |
| - | 忽略字段 | xml:"-" |
常见错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
| XML syntax error | 语法错误 | 检查标签闭合 |
| invalid character entity | 未转义字符 | 使用 < > |
| expected “=” | 属性格式错误 | 属性值加引号 |
参考资料
最后更新:2026-04-03
Go 版本:Go 1.23+