Интерфейс xml.Unmarshaler
Интерфейс xml.Unmarshaler позволяет определить собственный способ
распаковки XML-данных в структуру. Он содержит единственный метод
UnmarshalXML, который получает на вход указатель на
декодер *xml.Decoder и начальный элемент
xml.StartElement. Реализация этого метода полностью
контролирует процесс чтения и парсинга XML-данных для конкретного типа.
Синтаксис
type Unmarshaler interface {
UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
}
Пример
Давайте реализуем кастомную распаковку для структуры, которая содержит цену товара в виде строки:
package 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)
}
Результат выполнения кода:
"Name: Laptop, Price: 999"
Пример
Реализуем кастомную распаковку для структуры, которая обрабатывает атрибуты и вложенные элементы:
package 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)
}
Результат выполнения кода:
"ID: 42, Name: John, IsAdmin: true"
Пример
Используем кастомную распаковку для обработки списка элементов с возможностью пропуска некорректных данных:
package 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)
}
Результат выполнения кода:
[10 1 1 7]
-
интерфейс
xml.Marshaler,
который определяет кастомную упаковку в XML -
интерфейс
json.Unmarshaler,
который реализует кастомную распаковку JSON -
функцию
xml.Unmarshal,
которая выполняет распаковку XML-данных -
тип
xml.Decoder,
который используется для потокового чтения XML