8.2.2. XML Serialization та LINQ to XML
8.2.2. XML Serialization та LINQ to XML
Вступ: XML у сучасному світі
XML (eXtensible Markup Language) — це текстовий формат для представлення структурованих даних, який домінував у світі веб-сервісів до появи JSON. Хоча JSON витіснив XML у багатьох сценаріях (особливо REST API), XML все ще широко використовується у:
- Корпоративні системи: SOAP веб-сервіси, Enterprise Service Bus (ESB)
- Конфігураційні файли:
web.config,app.config, MSBuild (.csproj) - Офісні документи: Microsoft Office (
.docx,.xlsx— це ZIP архіви з XML всередині) - Android розробка: Layout файли, Manifest
- Фінансові та медичні системи: HL7, FHIR, FpML (строгі стандарти вимагають XML Schema)
XML vs JSON: Коли що використовувати?
| Критерій | XML | JSON |
|---|---|---|
| Читабельність | Вербозний | Компактний |
| Розмір | Більший (теги, закриваючі теги) | Менший |
| Швидкість парсингу | Повільніше | Швидше |
| Типізація | XML Schema (XSD) validation | JSON Schema (менш поширений) |
| Коментарі | ✅ Підтримка <!-- --> | ❌ Немає |
| Атрибути | ✅ <person age="25"> | ❌ Лише значення |
| Namespaces | ✅ Сильна підтримка | ❌ Немає |
| Використання | Legacy, Enterprise, Документи | Modern APIs, Web, Mobile |
Приклад порівняння:
<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Олександр</name>
<age>30</age>
<isStudent>true</isStudent>
<skills>
<skill>C#</skill>
<skill>SQL</skill>
</skills>
</person>
{
"name": "Олександр",
"age": 30,
"isStudent": true,
"skills": ["C#", "SQL"]
}
XmlSerializer: Класичний підхід
XmlSerializer — це вбудований .NET клас для серіалізації об'єктів у XML та назад. Він використовує reflection та атрибути для контролю XML структури.
Базова серіалізація
using System;
using System.IO;
using System.Xml.Serialization;
[XmlRoot("Student")] // Кореневий елемент XML
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public double GPA { get; set; }
}
class XmlSerializationBasics
{
static void Main()
{
var student = new Student
{
Id = 101,
Name = "Марина Коваль",
GPA = 4.5
};
// СЕРІАЛІЗАЦІЯ в XML
var serializer = new XmlSerializer(typeof(Student));
using (var writer = new StringWriter())
{
serializer.Serialize(writer, student);
string xml = writer.ToString();
Console.WriteLine("=== XML ===");
Console.WriteLine(xml);
}
/* Вивід:
<?xml version="1.0" encoding="utf-16"?>
<Student xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Id>101</Id>
<Name>Марина Коваль</Name>
<GPA>4.5</GPA>
</Student>
*/
// ДЕСЕРІАЛІЗАЦІЯ з XML
string xmlInput = @"
<Student>
<Id>202</Id>
<Name>Ігор Петренко</Name>
<GPA>3.8</GPA>
</Student>";
using (var reader = new StringReader(xmlInput))
{
var deserialized = (Student)serializer.Deserialize(reader);
Console.WriteLine($"\n=== ДЕСЕРІАЛІЗОВАНО ===");
Console.WriteLine($"ID: {deserialized.Id}, Ім'я: {deserialized.Name}, GPA: {deserialized.GPA}");
}
}
}
Розбір коду:
- Рядок 5:
[XmlRoot("Student")]задає назву кореневого елемента (необов'язково, за замовчуванням — назва класу). - Рядок 25: Створюємо
XmlSerializerдля типуStudent. - Рядок 28:
Serialize(TextWriter, object)записує XML у потік. - Рядок 53:
Deserialize(TextReader)читає XML та створює об'єкт.
XmlSerializer додає XML Schema namespaces (xmlns:xsi, xmlns:xsd). Для їх видалення див. приклад нижче.Видалення namespaces
var serializer = new XmlSerializer(typeof(Student));
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", ""); // Порожній namespace
using (var writer = new StringWriter())
{
serializer.Serialize(writer, student, namespaces);
Console.WriteLine(writer.ToString());
}
/* Вивід (без namespaces):
<?xml version="1.0" encoding="utf-16"?>
<Student>
<Id>101</Id>
<Name>Марина Коваль</Name>
<GPA>4.5</GPA>
</Student>
*/
Атрибути серіалізації XML
XmlElement: Налаштування елементів
using System;
using System.Xml.Serialization;
public class Book
{
[XmlElement("BookID")]
public int Id { get; set; }
[XmlElement("Title")]
public string BookTitle { get; set; }
[XmlElement("Author")]
public string AuthorName { get; set; }
}
Результат XML:
<Book>
<BookID>1</BookID>
<Title>Clean Code</Title>
<Author>Robert Martin</Author>
</Book>
Без атрибутів було б: <Id>1</Id>, <BookTitle>Clean Code</BookTitle> тощо.
XmlAttribute: Властивості як атрибути
using System;
using System.Xml.Serialization;
[XmlRoot("Product")]
public class Product
{
[XmlAttribute("id")]
public int ProductId { get; set; }
[XmlAttribute("category")]
public string Category { get; set; }
[XmlElement("Name")]
public string ProductName { get; set; }
public decimal Price { get; set; }
}
Результат XML:
<Product id="42" category="Electronics">
<Name>Ноутбук</Name>
<Price>25000</Price>
</Product>
Розбір:
[XmlAttribute]— значення стає атрибутом тега (id="42").[XmlElement]або без атрибуту — значення стає вкладеним елементом (<Name>...</Name>).
- Атрибути: Метадані, ID, прості значення (не можуть містити вкладені об'єкти).
- Елементи: Складні дані, колекції, вкладені об'єкти.
XmlIgnore: Виключення властивостей
public class User
{
public string Username { get; set; }
[XmlIgnore]
public string PasswordHash { get; set; } // НЕ серіалізується!
public string Email { get; set; }
}
Результат XML:
<User>
<Username>vasyl_ua</Username>
<Email>vasyl@example.com</Email>
</User>
PasswordHash повністю відсутній у XML (аналогічно [JsonIgnore]).
XmlArray та XmlArrayItem: Колекції
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
public class Course
{
public string CourseName { get; set; }
[XmlArray("Participants")]
[XmlArrayItem("Student")]
public List<string> Students { get; set; }
}
Результат XML:
<Course>
<CourseName>C# Advanced</CourseName>
<Participants>
<Student>Олена</Student>
<Student>Дмитро</Student>
<Student>Анна</Student>
</Participants>
</Course>
Без атрибутів було б:
<Students>
<string>Олена</string>
<string>Дмитро</string>
</Students>
[XmlArrayItem] для читабельності XML (інакше будуть використані назви типів, наприклад <string>).Робота з складними об'єктами
Вкладені об'єкти
using System;
using System.IO;
using System.Xml.Serialization;
public class Address
{
public string City { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
}
[XmlRoot("Employee")]
public class Employee
{
[XmlAttribute("id")]
public int Id { get; set; }
public string FullName { get; set; }
[XmlElement("HomeAddress")]
public Address Address { get; set; }
}
class NestedObjectsDemo
{
static void Main()
{
var employee = new Employee
{
Id = 12345,
FullName = "Олександра Бондаренко",
Address = new Address
{
City = "Львів",
Street = "вул. Шевченка, 10",
ZipCode = "79000"
}
};
var serializer = new XmlSerializer(typeof(Employee));
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
using (var writer = new StringWriter())
{
serializer.Serialize(writer, employee, namespaces);
Console.WriteLine(writer.ToString());
}
}
}
Результат XML:
<?xml version="1.0" encoding="utf-16"?>
<Employee id="12345">
<FullName>Олександра Бондаренко</FullName>
<HomeAddress>
<City>Львів</City>
<Street>вул. Шевченка, 10</Street>
<ZipCode>79000</ZipCode>
</HomeAddress>
</Employee>
Колекції складних об'єктів
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
public class Task
{
[XmlAttribute("id")]
public int TaskId { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
}
[XmlRoot("TaskList")]
public class TaskList
{
[XmlArray("Tasks")]
[XmlArrayItem("Task")]
public List<Task> Tasks { get; set; }
}
class CollectionDemo
{
static void Main()
{
var taskList = new TaskList
{
Tasks = new List<Task>
{
new Task { TaskId = 1, Description = "Написати звіт", IsCompleted = true },
new Task { TaskId = 2, Description = "Провести зустріч", IsCompleted = false },
new Task { TaskId = 3, Description = "Оновити документацію", IsCompleted = false }
}
};
var serializer = new XmlSerializer(typeof(TaskList));
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
using (var writer = new StringWriter())
{
serializer.Serialize(writer, taskList, namespaces);
Console.WriteLine(writer.ToString());
}
}
}
Результат XML:
<?xml version="1.0" encoding="utf-16"?>
<TaskList>
<Tasks>
<Task id="1">
<Description>Написати звіт</Description>
<IsCompleted>true</IsCompleted>
</Task>
<Task id="2">
<Description>Провести зустріч</Description>
<IsCompleted>false</IsCompleted>
</Task>
<Task id="3">
<Description>Оновити документацію</Description>
<IsCompleted>false</IsCompleted>
</Task>
</Tasks>
</TaskList>
LINQ to XML: Сучасний підхід
XmlSerializer чудовий для серіалізації об'єктів, але що якщо потрібно:
- Створити XML програмно (без класів)?
- Запитати конкретні елементи з великого XML?
- Модифікувати існуючий XML?
Для цього використовується LINQ to XML — функціональний API для роботи з XML через класи XDocument, XElement, XAttribute.
Створення XML з XElement
using System;
using System.Xml.Linq;
class LinqToXmlCreation
{
static void Main()
{
// Створюємо XML програмно
var xml = new XElement("Library",
new XElement("Book",
new XAttribute("isbn", "978-0-13-468599-1"),
new XElement("Title", "Clean Code"),
new XElement("Author", "Robert C. Martin"),
new XElement("Year", 2008)
),
new XElement("Book",
new XAttribute("isbn", "978-0-201-61622-4"),
new XElement("Title", "The Pragmatic Programmer"),
new XElement("Author", "Andy Hunt"),
new XElement("Year", 1999)
)
);
Console.WriteLine(xml);
}
}
Результат:
<Library>
<Book isbn="978-0-13-468599-1">
<Title>Clean Code</Title>
<Author>Robert C. Martin</Author>
<Year>2008</Year>
</Book>
<Book isbn="978-0-201-61622-4">
<Title>The Pragmatic Programmer</Title>
<Author>Andy Hunt</Author>
<Year>1999</Year>
</Book>
</Library>
Розбір коду:
- Рядок 9:
XElement— це один XML елемент. - Рядок 10-14: Вкладені елементи передаються як параметри конструктора.
- Рядок 11:
XAttributeдодає атрибутisbn="...".
Створення з LINQ запитом
using System;
using System.Linq;
using System.Xml.Linq;
class LinqToXmlFromData
{
static void Main()
{
var students = new[]
{
new { Id = 1, Name = "Анна", Grade = 95 },
new { Id = 2, Name = "Богдан", Grade = 88 },
new { Id = 3, Name = "Вікторія", Grade = 92 }
};
// Генеруємо XML з масиву даних
var xml = new XElement("Students",
from student in students
where student.Grade >= 90
select new XElement("Student",
new XAttribute("id", student.Id),
new XElement("Name", student.Name),
new XElement("Grade", student.Grade)
)
);
Console.WriteLine(xml);
}
}
Результат:
<Students>
<Student id="1">
<Name>Анна</Name>
<Grade>95</Grade>
</Student>
<Student id="3">
<Name>Вікторія</Name>
<Grade>92</Grade>
</Student>
</Students>
Тільки студенти з оцінкою ≥90!
Запити до XML з LINQ
Завантаження XML
using System;
using System.Xml.Linq;
class LoadXmlDemo
{
static void Main()
{
string xmlString = @"
<Library>
<Book isbn='978-1'>
<Title>Book A</Title>
<Author>Author 1</Author>
<Price>29.99</Price>
</Book>
<Book isbn='978-2'>
<Title>Book B</Title>
<Author>Author 2</Author>
<Price>39.99</Price>
</Book>
</Library>";
// Завантаження з рядка
XDocument doc = XDocument.Parse(xmlString);
// Або з файлу:
// XDocument doc = XDocument.Load("library.xml");
Console.WriteLine(doc);
}
}
Запит елементів
using System;
using System.Linq;
using System.Xml.Linq;
class QueryXmlDemo
{
static void Main()
{
var xml = XElement.Parse(@"
<Library>
<Book isbn='978-1'>
<Title>Clean Code</Title>
<Author>Robert Martin</Author>
<Price>35.00</Price>
</Book>
<Book isbn='978-2'>
<Title>Design Patterns</Title>
<Author>Gang of Four</Author>
<Price>45.00</Price>
</Book>
<Book isbn='978-3'>
<Title>Refactoring</Title>
<Author>Martin Fowler</Author>
<Price>40.00</Price>
</Book>
</Library>");
// Запит 1: Всі назви книг
Console.WriteLine("=== ВСІ НАЗВИ ===");
var titles = xml.Descendants("Title").Select(t => t.Value);
foreach (var title in titles)
{
Console.WriteLine($"- {title}");
}
// Запит 2: Книги дорожче $40
Console.WriteLine("\n=== КНИГИ ДОРОЖЧЕ $40 ===");
var expensiveBooks = from book in xml.Elements("Book")
let price = (decimal)book.Element("Price")
where price > 40
select new
{
Title = (string)book.Element("Title"),
Price = price
};
foreach (var book in expensiveBooks)
{
Console.WriteLine($"{book.Title}: ${book.Price}");
}
// Запит 3: Отримати атрибут ISBN
Console.WriteLine("\n=== ISBN КНИЖОК ===");
var isbns = xml.Elements("Book")
.Select(b => b.Attribute("isbn").Value);
foreach (var isbn in isbns)
{
Console.WriteLine(isbn);
}
}
}
Вивід:
=== ВСІ НАЗВИ ===
- Clean Code
- Design Patterns
- Refactoring
=== КНИГИ ДОРОЖЧЕ $40 ===
Design Patterns: $45.00
=== ISBN КНИЖОК ===
978-1
978-2
978-3
Розбір коду:
- Рядок 31:
Descendants("Title")знаходить всі елементи<Title>на будь-якій глибині. - Рядок 39:
Elements("Book")знаходить ПРЯМІ дочірні елементи<Book>. - Рядок 40:
(decimal)book.Element("Price")— explicit cast для конвертації XML елемента у тип. - Рядок 56:
Attribute("isbn").Valueотримує значення атрибуту.
Elements("Name")— тільки прямі нащадки.Descendants("Name")— всідіректорно (усі рівні вглиб).
Модифікація XML
Додавання елементів
using System;
using System.Xml.Linq;
class ModifyXmlDemo
{
static void Main()
{
var library = XElement.Parse(@"
<Library>
<Book>
<Title>Existing Book</Title>
</Book>
</Library>");
// Додаємо нову книгу
library.Add(
new XElement("Book",
new XElement("Title", "New Book"),
new XElement("Author", "New Author")
)
);
Console.WriteLine(library);
}
}
Результат:
<Library>
<Book>
<Title>Existing Book</Title>
</Book>
<Book>
<Title>New Book</Title>
<Author>New Author</Author>
</Book>
</Library>
Оновлення значень
var priceElement = xml.Descendants("Price").First();
priceElement.Value = "29.99"; // Оновлюємо ціну
// Або через SetValue:
priceElement.SetValue(29.99m);
Видалення елементів
// Видаляємо всі книги дорожче $40
xml.Elements("Book")
.Where(b => (decimal)b.Element("Price") > 40)
.Remove();
Порівняння: XmlSerializer vs LINQ to XML
| Критерій | XmlSerializer | LINQ to XML |
|---|---|---|
| Призначення | Object ↔ XML serialization | XML маніпуляція та запити |
| Підхід | Декларативний (атрибути) | Імперативний (код) |
| Типізація | Strongly-typed (класи) | Динамічний (XElement) |
| Запити | ❌ Немає | ✅ Потужні LINQ запити |
| Модифікація | ❌ Треба змінювати об'єкт | ✅ Пряма модифікація XML |
| Швидкість | Швидка для серіалізації | Швидка для запитів |
| Коли використовувати | DTO serialization, API | XML processing, конфігурації |
- XmlSerializer — для серіалізації бізнес-об'єктів (API, збереження стану).
- LINQ to XML — для обробки XML документів (читання конфігурацій, парсинг RSS, трансформації).
Практичні завдання
Завдання 1: RSS Feed Parser
Створіть парсер RSS feed:
- Завантажте XML з URL (наприклад,
https://example.com/rss.xml). - Використайте LINQ to XML для витягування: назва статті, дата, посилання.
- Виведіть топ-5 останніх статей.
Завдання 2: Configuration Manager
Реалізуйте систему конфігурацій на XML:
- Клас
AppConfigз властивостямиDatabaseConnection,LogLevel,Features. - Серіалізація у
app.config.xmlза допомогоюXmlSerializer. - Метод для оновлення одного параметру через LINQ to XML (без повної десеріалізації).
Завдання 3: XML to JSON Converter
Створіть конвертер:
- Зчитайте XML файл через
XDocument. - Трансформуйте його у об'єкт C# (dynamic або конкретний клас).
- Серіалізуйте у JSON за допомогою
System.Text.Json. - Порівняйте розміри вихідного XML та JSON.
Резюме
XmlSerializer
Атрибути
[XmlRoot], [XmlElement], [XmlAttribute], [XmlIgnore], [XmlArray] — точний контроль серіалізації.LINQ to XML
XDocument, XElement, XAttribute.Запити
BinaryFormatter.