Интерфейс xml.Marshaler

Интерфейс xml.Marshaler из пакета encoding/xml позволяет типам данных управлять своим преобразованием в XML. Он содержит единственный метод MarshalXML, который принимает указатель на xml.Encoder и начальный элемент xml.StartElement, а возвращает ошибку. Реализация этого метода дает полный контроль над тем, как структура будет представлена в XML-формате.

Синтаксис

gotype Marshaler interface {
  MarshalXML(e *Encoder, start StartElement) error
}

Базовый пример

Рассмотрим простую структуру, реализующую интерфейс xml.Marshaler. В этом примере мы вручную конструируем XML-элемент с атрибутами:

gopackage main

import (
  "encoding/xml"
  "fmt"
  "os"
)

type User struct {
  ID   int
  Name string
}

func (u User) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
  start.Name = xml.Name{Local: "user"}
  start.Attr = []xml.Attr{
    {Name: xml.Name{Local: "id"}, Value: fmt.Sprintf("%d", u.ID)},
  }
  err := e.EncodeToken(start)
  if err != nil {
    return err
  }
  err = e.EncodeElement(u.Name, xml.StartElement{Name: xml.Name{Local: "name"}})
  if err != nil {
    return err
  }
  return e.EncodeToken(start.End())
}

func main() {
  user := User{ID: 1, Name: "Alice"}
  output, err := xml.MarshalIndent(user, "", "  ")
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  fmt.Println(string(output))
}

Результат выполнения кода:

go<user id="1">
  <name>Alice</name>
</user>

Пример со вложенными элементами

В этом примере мы создаем более сложную XML-структуру с несколькими уровнями вложенности, используя методы EncodeToken и EncodeElement для построения иерархии:

gopackage main

import (
  "encoding/xml"
  "fmt"
  "os"
)

type Address struct {
  City  string
  Country string
}

type Person struct {
  FullName string
  Location Address
}

func (p Person) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
  start.Name = xml.Name{Local: "person"}
  err := e.EncodeToken(start)
  if err != nil {
    return err
  }
  err = e.EncodeElement(p.FullName, xml.StartElement{Name: xml.Name{Local: "full_name"}})
  if err != nil {
    return err
  }
  err = e.EncodeElement(p.Location, xml.StartElement{Name: xml.Name{Local: "address"}})
  if err != nil {
    return err
  }
  return e.EncodeToken(start.End())
}

func main() {
  person := Person{
    FullName: "John Doe",
    Location: Address{City: "New York", Country: "USA"},
  }
  output, err := xml.MarshalIndent(person, "", "  ")
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  fmt.Println(string(output))
}

Результат выполнения кода:

go<person>
  <full_name>John Doe</full_name>
  <address>
  <City>New York</City>
  <Country>USA</Country>
  </address>
</person>

Пример с изменением имен полей

Реализация интерфейса позволяет изменить имена полей в XML, используя пользовательские теги или динамическое формирование имен:

gopackage main

import (
  "encoding/xml"
  "fmt"
  "os"
)

type Product struct {
  ID     int
  Title  string
  Price  float64
  InStock  bool
}

func (p Product) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
  start.Name = xml.Name{Local: "product"}
  err := e.EncodeToken(start)
  if err != nil {
    return err
  }
  err = e.EncodeElement(p.ID, xml.StartElement{Name: xml.Name{Local: "product_id"}})
  if err != nil {
    return err
  }
  err = e.EncodeElement(p.Title, xml.StartElement{Name: xml.Name{Local: "product_title"}})
  if err != nil {
    return err
  }
  err = e.EncodeElement(p.Price, xml.StartElement{Name: xml.Name{Local: "product_price"}})
  if err != nil {
    return err
  }
  stockStatus := "no"
  if p.InStock {
    stockStatus = "yes"
  }
  err = e.EncodeElement(stockStatus, xml.StartElement{Name: xml.Name{Local: "in_stock"}})
  if err != nil {
    return err
  }
  return e.EncodeToken(start.End())
}

func main() {
  product := Product{ID: 101, Title: "Laptop", Price: 999.99, InStock: true}
  output, err := xml.MarshalIndent(product, "", "  ")
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  fmt.Println(string(output))
}

Результат выполнения кода:

go<product>
  <product_id>101</product_id>
  <product_title>Laptop</product_title>
  <product_price>999.99</product_price>
  <in_stock>yes</in_stock>
</product>

Смотрите также

  • интерфейс xml.Unmarshaler,
    который позволяет управлять парсингом XML
  • интерфейс json.Marshaler,
    который аналогичен для JSON
  • функцию xml.Marshal,
    которая преобразует данные в XML без отступов
  • функцию xml.MarshalIndent,
    которая создает XML с форматированием