Исключение при XML Deserialize
Как бросить исключение при десериализации если свойство в xml не будет найдено?
Например у меня был класс Persons и я его успешно сериализовал:
[Serializable]
[XmlRoot("Persons")]
public class Persons
{
[XmlArray(nameof(Names)), XmlArrayItem(typeof(string), ElementName = "Name")]
public List<string> Names { get; set; }
}
xml:
<?xml version="1.0"?>
<Persons xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Names>
<Name>Ivan</Name>
<Name>Alena</Name>
</Names>
</Persons>
Затем мне понадобилось Names в классе Persons изменить на FullNames:
[Serializable]
[XmlRoot("Persons")]
public class Persons
{
[XmlArray(nameof(FullNames)), XmlArrayItem(typeof(string), ElementName = "Name")]
public List<string> FullNames { get; set; }
}
Как можно получить исключение при десериализации в случае когда FullNames не будет найдено в xml (предварительно xml не менялся, в нем все тот же старый Names):
try
{
using var fs = new FileStream(path, FileMode.Open);
person = (Persons)serializer.Deserialize(fs);
}
catch (Exception ex)
{
// Обработка исключения
}
Нужно чтобы обновить xml, создать новую по умолчанию с новым именем свойства. Сейчас я просто получаю пустой список FullNames.
Ответы (3 шт):
Для XML ответ не актуален, только для JSON.
Но вдруг кому пригодится.
В .NET 7
можно использовать атрибут [JsonRequired]
:
[JsonRequired]
public List<string> FullNames { get; set; }
Либо модификатор required
из C# 11
:
public required List<string> FullNames { get; set; }
Автор ввёл всех (во всяком случае, меня) в заблуждение фразой
Как бросить исключение при десериализации если свойство в xml не будет найдено?
и заключив код десериализации в блок try-catch
.
Что означает исключение? Что произошло нечто из ряда вон выходящее и нужно откзаться от уже считанных данных. И в блоке catch
заполнить класс дефолтными значениями.
В такой трактовке проблема решается следующим образом.
У сериализатора есть события. Можно на них подписаться и в событии выбросить исключение.
var serializer = new XmlSerializer(typeof(Persons));
serializer.UnknownElement += Serializer_UnknownElement;
Persons person;
try
{
using var fs = new FileStream(path, FileMode.Open);
person = (Persons)serializer.Deserialize(fs);
}
catch (Exception ex)
{
person = new Persons { FullNames = new List<string> { "A", "B" } };
}
void Serializer_UnknownElement(object? sender, XmlElementEventArgs e)
{
throw new NotImplementedException();
}
Здесь в обработчике исключения класс Persons
создаётся со значениями по умолчанию.
Но, насколько я понял, нужно не прерывать процесс десериализации, а прочитать данные из XML со старыми названиями.
Это можно сделать множеством способов.
Десериализуем XML в класс
PersonsDTO
, который имеет свойствоNames
. После чего мапим DTO на нужный объектPersons
. Собственно, это стандартный современный способ с использованием промежуточного типа для передачи данных из слоя в слой.Опять используем событие и в нём вручную парсим вложенные элементы XML:
void Serializer_UnknownElement(object? sender, XmlElementEventArgs e)
{
if (e.Element.Name == "Names")
{
var persons = (Persons)e.ObjectBeingDeserialized;
var values = e.Element.ChildNodes.OfType<XmlElement>().Select(x => x.InnerText);
persons.FullNames.AddRange(values);
}
}
В данном конкретном случае это просто, потому что нам нужно разобрать всего один уровень вложенности с простым типом.
Но в общем случае это может быть трудоёмко. Мы же для того и взяли десериализатор, чтобы избежать ручного парсинга?
- Кастомный XmlReader. Мой любимый способ.
Пишем класс-читатель, который на лету подменяет одно название элемента на другое. В нашем случаеNames
меняется наFullNames
.
class NamesXmlReader : XmlTextReader
{
public NamesXmlReader(string url) : base(url) { }
public override string LocalName
{
get
{
if (base.LocalName == "Names")
return "FullNames";
return base.LocalName;
}
}
}
Далее, соответственно, используем его:
using var reader = new NamesXmlReader(path);
person = (Persons)serializer.Deserialize(reader);
Однако, нужно быть осторожным, т. к. в данном случае все элементы Names
будут переименованы. А в сложном XML вполне могут быть совпадающие имена на разных уровня вложенности и в разных неймспейсах. В таком случае нужно вводить дополнительные проверки.
Можно реализовать интерфейс
IXmlSerializable
у нашего типаPersons
. Но это получается, по сути, ручной парсинг.Также можно с помощью LINQ2XML прочитать XML, что тоже является ручным разбором и отказом от автоматической десериализации. Хотя во многих случаях это реально проще, чем добиться от
XmlSerializer'а
желаемого поведения.
Чтобы разобраться с этим вопросом, нужно понимать общую логику. Во-первых, XML это не совсем то же самое, что JSON. Во-вторых, парсер XML справедливо считает, что нужный класс просто не найден (есть другой класс).
Поэтому, чтение данных не поможет. Нужно извлечь из XML схему данных и проверить, есть ли в ней элемент "FullNames". Возможно, это делается так, пример.