1

I have a List of Book in an XML file. Book has a property List<string> Authors:

public List<string> Authors
{
    get => _authors;
    set
    {
        if (value == null) throw new ArgumentNullException(nameof(value));

        _authors = value.Any()
            ? value : throw new ArgumentOutOfRangeException(nameof(value), @"Must have at least one author.");
    }
}

So, as I understand, when I deserialize it with XmlSerializer, it tries to give the property an empty list first and then to fill it with values, which doen's work, because the setter throws an exception. What may be a solution here?

Deserialization:

using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

var xmlSerializer = new XmlSerializer(typeof(List<Book>));
data = (List<Book>)xmlSerializer.Deserialize(fileStream);

upd: XML

<?xml version="1.0"?>
<ArrayOfBook xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Book>
    <Id>0</Id>
    <Name>Unspecified</Name>
    <YearPublished>2000</YearPublished>
    <PagesNumber>200</PagesNumber>
    <Annotation>No annotation.</Annotation>
    <Price>50</Price>
    <StandardNumber>1</StandardNumber>
    <Authors>
      <string>Author1</string>
    </Authors>
    <CityPublished>Unspecified</CityPublished>
    <Publisher>Unspecified</Publisher>
    <CopiesNumber>300</CopiesNumber>
  </Book>
  <Book>
    <Id>0</Id>
    <Name>Unspecified</Name>
    <YearPublished>2000</YearPublished>
    <PagesNumber>200</PagesNumber>
    <Annotation>No annotation.</Annotation>
    <Price>50</Price>
    <StandardNumber>2</StandardNumber>
    <Authors>
      <string>Author2</string>
    </Authors>
    <CityPublished>Unspecified</CityPublished>
    <Publisher>Unspecified</Publisher>
    <CopiesNumber>300</CopiesNumber>
  </Book>
  <Book>
    <Id>0</Id>
    <Name>Unspecified</Name>
    <YearPublished>2000</YearPublished>
    <PagesNumber>200</PagesNumber>
    <Annotation>No annotation.</Annotation>
    <Price>50</Price>
    <StandardNumber>3</StandardNumber>
    <Authors>
      <string>Author3</string>
    </Authors>
    <CityPublished>Unspecified</CityPublished>
    <Publisher>Unspecified</Publisher>
    <CopiesNumber>300</CopiesNumber>
  </Book>
</ArrayOfBook>

upd2: Chosen solution

[XmlIgnore]
[JsonIgnore]
[IgnoreDataMember]
public List<string> Authors
{
    get => new(_authors);
    set
    {
        if (value == null) throw new ArgumentNullException(nameof(value));

        _authors = value.Any()
            ? value : throw new ArgumentOutOfRangeException(nameof(value), @"Must have at least one author.");
    }
}

[XmlArray(nameof(Authors))]
[JsonProperty(nameof(Authors))]
public string[] AuthorsArray
{
    get => Authors.ToArray();
    set
    {
        if (value == null || !value.Any()) return;

        if (_authors == null)
        {
            Authors = new List<string>(value);
        }
        else
        {
            Authors.AddRange(value);
        }
    }
}
  • Show your xml please – Vivek Nuna Oct 10 '21 at 12:53
  • I wonder what the constructor of this class does. – Klaus Gütter Oct 10 '21 at 13:13
  • I'm not sure and I can't try it atm, but have you tried applying an [XmlArrayAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlarrayattribute?view=net-5.0) to your property? Beware - that would also change the xml file layout. Another option would be to implement the IXmlSerializable interface to get full control of the serialization/deserialization process. – Steeeve Oct 10 '21 at 13:18
  • It's not a class, it's a record. I didn't specify a constructor, just getters and setters. – Pavel Antropov Oct 10 '21 at 13:27
  • @Steeeve I'm not sure I understand how the attribute might help and how exactly I should use it. – Pavel Antropov Oct 10 '21 at 13:32
  • @vivek nuna I added the xml – Pavel Antropov Oct 10 '21 at 13:33
  • feels to me like you're putting the business logic condition of "must always be 1 or more authors" in the wrong place. This class is being used to deserialise xml so give it that purpose only, then have a class elsewhere which consumes the deserialized object and then does the error checking. Another option is to use an xml schema that contained that rule – Tim Rutter Oct 10 '21 at 13:35
  • @PavelAntropov it is just an idea, maybe that way the complete array get's deserialized before assigning the property value. – Steeeve Oct 10 '21 at 13:36
  • @Tim Rutter thanks, probably I will just take the error checking from the properties to a separate class how you suggest, if I don't find another solution, that's what I was thinking too. – Pavel Antropov Oct 10 '21 at 13:38
  • @Steeeve I tried it but nothing changed. But I might be doing it wrong. – Pavel Antropov Oct 10 '21 at 13:40
  • You could mark `Authors` with `[XmlIgnore]` and serialize the list of authors using a surrogate array property as shown in [this answer](https://stackoverflow.com/a/45360185/3744182) to [C# Xml Serializer deserializes list to count of 0 instead of null](https://stackoverflow.com/q/45358844/3744182). Does that answer your question? if not, only other options would seem to be to implement `IXmlSerializable` or replace your model with a DTO. – dbc Oct 10 '21 at 16:22
  • 1
    (In any event checking for the list length in the setter seems sketchy; `List` is a mutable collection so a caller could just get the list, remove the contents, and never set the list back.) – dbc Oct 10 '21 at 16:23
  • 1
    @dbc that seems to work for me, thanks! – Pavel Antropov Oct 10 '21 at 17:34

0 Answers0