Go语言标准库encoding/xml提供了对XML数据进行序列化和反序列化的能力。其中xml.Marshal函数可以把结构体、切片、映射等Go语言数据转换为XML格式的字节切片。但这个函数生成的XML几乎没有可读性,所有元素都会紧密排列在同一行里。比如下面这段代码:

package main
import (
"encoding/xml"
"fmt"
)
type Person struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
Age int `xml:"age"`
City string `xml:"city"`
}
func main() {
p := Person{
Name: "张三",
Age: 28,
City: "上海",
}
data, _ := xml.Marshal(p)
fmt.Println(string(data))
}
运行这段代码,输出的结果是<person><name>张三</name><age>28</age><city>上海</city></person>,所有内容都粘在一起。如果结构体再复杂一些,几十个字段或者多层嵌套,这种输出基本没法用眼睛直接检查。所以encoding/xml还提供了MarshalIndent函数,专门用来生成带缩进和换行的格式化XML。
MarshalIndent函数的基本用法
MarshalIndent的函数签名是func MarshalIndent(v any, prefix, indent string) ([]byte, error)。和Marshal相比,它多出了两个字符串参数。第一个参数prefix会添加到每一行的开头,一般传空字符串即可;第二个参数indent是每一级缩进所使用的字符串,通常使用两个空格或者一个制表符。
以上面的Person结构体为例,只需要把xml.Marshal(p)替换成xml.MarshalIndent(p, "", " "),输出就会变成下面这种层级分明的格式:
package main
import (
"encoding/xml"
"fmt"
)
type Person struct {
XMLName xml.Name `xml:"person"`
Name string `xml:"name"`
Age int `xml:"age"`
City string `xml:"city"`
}
func main() {
p := Person{
Name: "张三",
Age: 28,
City: "上海",
}
data, _ := xml.MarshalIndent(p, "", " ")
fmt.Println(string(data))
}
实际执行后打印结果为:
<person> <name>张三</name> <age>28</age> <city>上海</city> </person>
可以看到,每个子元素都单独占一行,并且按照嵌套深度前方增加了两个空格。如果希望缩进更明显,可以把indent改为四个空格或者"\t"。需要注意的是,prefix参数会原样加到每一行的最前面,包括根元素行。如果传入" "作为前缀,那么根元素行前也会有两个空格,这种用法在一些需要整体缩进的场景下有用。
结构体标签与嵌套类型的处理
MarshalIndent和Marshal使用完全相同的序列化规则,因此结构体标签的写法完全一致。例如使用xml:"name,attr"可以把字段变成元素的属性,使用xml:",omitempty"可以在零值时省略该元素,使用xml:",chardata"可以输出文本节点。这些规则在带缩进的输出中同样适用。
下面演示一个更加复杂的例子,包含属性、嵌套结构体和切片。定义订单结构体Order,它里面有一个Customer结构体和一个Items切片:
package main
import (
"encoding/xml"
"fmt"
)
type Customer struct {
Name string `xml:"name,attr"`
City string `xml:"city"`
}
type Item struct {
ID string `xml:"id,attr"`
Name string `xml:"name"`
Price float64 `xml:"price"`
}
type Order struct {
XMLName xml.Name `xml:"order"`
ID string `xml:"id,attr"`
Customer Customer `xml:"customer"`
Items []Item `xml:"items>item"`
}
func main() {
order := Order{
ID: "20250301",
Customer: Customer{
Name: "李四",
City: "北京",
},
Items: []Item{
{ID: "1001", Name: "键盘", Price: 299.0},
{ID: "1002", Name: "鼠标", Price: 89.0},
},
}
data, _ := xml.MarshalIndent(order, "", " ")
fmt.Println(string(data))
}
运行这段代码,输出如下:
<order id="20250301">
<customer name="李四">
<city>北京</city>
</customer>
<items>
<item id="1001">
<name>键盘</name>
<price>299</price>
</item>
<item id="1002">
<name>鼠标</name>
<price>89</price>
</item>
</items>
</order>
可以看到,属性会紧跟在起始标签内部,不会单独换行;嵌套结构体Customer被放在order标签下,并正确缩进;Items切片中的每一项都作为独立的item元素,数组的层级同样通过缩进体现。这种输出可以直接保存成.xml文件或者通过接口返回给前端,阅读和调试都非常方便。
使用MarshalIndent时的注意事项
虽然MarshalIndent用起来简单,但也有一些细节需要留意。首先,缩进字符串会在每一层递归时重复拼接。对于非常深的嵌套结构(比如超过几十层),生成的字节切片可能会比紧凑格式大不少,内存占用也会上升。如果只是日志输出或者小规模配置生成,这点开销可以忽略;但如果要序列化海量数据或者高频调用,建议评估性能影响。
其次,MarshalIndent返回的字节切片是UTF-8编码的,XML声明<?xml version="1.0" encoding="UTF-8"?>并不会自动添加。如果需要标准XML头,可以手动在输出前拼接字符串。另外,如果结构体中包含[]byte类型的字段,XML包会将其进行Base64编码,此时缩进效果正常,但可读性取决于二进制内容本身。
还有一点,当使用xml:",chardata"或者直接使用字符串字段时,文本内容中的空白字符(空格、换行)会被保留。如果源数据本身包含换行符,缩进可能会和这些字符混合,导致视觉上不够整齐。解决方法是手动清理数据中的控制字符,或者尝试配合xml.Decoder进行规范化处理。总的来说,MarshalIndent是应对“漂亮输出XML”需求的最佳选择,绝大多数场景下直接调用即可满足要求。
Go语言XML美化输出MarshalIndent修改时间:2026-09-22 22:01:02