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

Интерфейс xml.Unmarshaler позволяет определить собственный способ распаковки XML-данных в структуру. Он содержит единственный метод UnmarshalXML, который получает на вход указатель на декодер *xml.Decoder и начальный элемент xml.StartElement. Реализация этого метода полностью контролирует процесс чтения и парсинга XML-данных для конкретного типа.

Синтаксис

gotype Unmarshaler interface {
  UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
}

Пример

Давайте реализуем кастомную распаковку для структуры, которая содержит цену товара в виде строки:

gopackage main

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

type Price string

func (p *Price) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
  var value string
  if err := d.DecodeElement(&value, &start); err != nil {
    return err
  }
  // Убираем символ валюты и пробелы
  cleaned := strings.TrimSpace(strings.TrimSuffix(value, "$"))
  *p = Price(cleaned)
  return nil
}

type Product struct {
  XMLName xml.Name `xml:"product"`
  Name  string   `xml:"name"`
  Price   Price  `xml:"price"`
}

func main() {
  xmlData := `<product><name>Laptop</name><price>999$</price></product>`
  var product Product
  
  if err := xml.Unmarshal([]byte(xmlData), &product); err != nil {
    fmt.Println(err)
    return
  }
  
  fmt.Printf("Name: %s, Price: %s", product.Name, product.Price)
}

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

go"Name: Laptop, Price: 999"

Пример

Реализуем кастомную распаковку для структуры, которая обрабатывает атрибуты и вложенные элементы:

gopackage main

import (
  "encoding/xml"
  "fmt"
)

type User struct {
  ID    int
  Name  string
  IsAdmin bool
}

func (u *User) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
  // Считываем атрибут id
  var id int
  for _, attr := range start.Attr {
    if attr.Name.Local == "id" {
      fmt.Sscanf(attr.Value, "%d", &id)
      u.ID = id
    }
  }

  // Считываем вложенные элементы
  var name, role string
  for {
    token, err := d.Token()
    if err != nil {
      return err
    }
    switch elem := token.(type) {
    case xml.StartElement:
      if elem.Name.Local == "name" {
        if err := d.DecodeElement(&name, &elem); err != nil {
          return err
        }
        u.Name = name
      }
      if elem.Name.Local == "role" {
        if err := d.DecodeElement(&role, &elem); err != nil {
          return err
        }
        u.IsAdmin = (role == "admin")
      }
    case xml.EndElement:
      if elem.Name == start.Name {
        return nil
      }
    }
  }
}

func main() {
  xmlData := `<user id="42"><name>John</name><role>admin</role></user>`
  var user User
  
  if err := xml.Unmarshal([]byte(xmlData), &user); err != nil {
    fmt.Println(err)
    return
  }
  
  fmt.Printf("ID: %d, Name: %s, IsAdmin: %t", user.ID, user.Name, user.IsAdmin)
}

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

go"ID: 42, Name: John, IsAdmin: true"

Пример

Используем кастомную распаковку для обработки списка элементов с возможностью пропуска некорректных данных:

gopackage main

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

type PositiveInt int

func (pi *PositiveInt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
  var value string
  if err := d.DecodeElement(&value, &start); err != nil {
    return err
  }
  
  num, err := strconv.Atoi(value)
  if err != nil || num <= 0 {
    // Устанавливаем значение по умолчанию для некорректных данных
    *pi = 1
    return nil
  }
  
  *pi = PositiveInt(num)
  return nil
}

type Data struct {
  XMLName xml.Name     `xml:"data"`
  Numbers []PositiveInt  `xml:"number"`
}

func main() {
  xmlData := `<data><number>10</number><number>-5</number><number>abc</number><number>7</number></data>`
  var data Data
  
  if err := xml.Unmarshal([]byte(xmlData), &data); err != nil {
    fmt.Println(err)
    return
  }
  
  fmt.Println(data.Numbers)
}

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

go[10 1 1 7]
  • интерфейс xml.Marshaler,
    который определяет кастомную упаковку в XML
  • интерфейс json.Unmarshaler,
    который реализует кастомную распаковку JSON
  • функцию xml.Unmarshal,
    которая выполняет распаковку XML-данных
  • тип xml.Decoder,
    который используется для потокового чтения XML