David Adams
David Adams

Reputation: 13

DataContractSerializer not deserializing correctly

using System;
using System.Runtime.Serialization;
using System.Xml;
using System.IO;
using System.Collections.Generic;

[DataContract(Name = "Book", Namespace = "")]
public class Book
{
    [DataMember] public string Title { get; set; }
    [DataMember] public string Author { get; set; }
    [DataMember] public int Year { get; set; }
}

[DataContract(Name = "Library", Namespace = "")]
public class Library
{
    [DataMember] public string Name { get; set; }
    [DataMember] public List<Book> Books { get; set; }
}

public class Program
{
    static string xml = @"<?xml version=""1.0"" encoding=""utf-8""?>
    <Library xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
        <Name>Central Library</Name>
        <Books>
            <Book>
                <Title>The Great Gatsby</Title>
                <Author>F. Scott Fitzgerald</Author>
                <Year>1925</Year>
            </Book>
            <Book>
                <Title>To Kill a Mockingbird</Title>
                <Author>Harper Lee</Author>
                <Year>1960</Year>
            </Book>
        </Books>
    </Library>";

    static void Main(string[] args)
    {
        try
        {
            Library library = DeserializeLibrary(xml);
            Console.WriteLine($"Deserialized Library: {library.Name}");
            foreach (var book in library.Books)
            {
                Console.WriteLine($"Book: {book.Title} by {book.Author} ({book.Year})");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine($"Deserialization failed: {e.Message}");
            Console.WriteLine($"Stack trace: {e.StackTrace}");
        }

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }

    /// <summary>
    /// Deserializes the XML string into a Library object using DataContractSerializer
    /// </summary>
    static Library DeserializeLibrary(string xmlString)
    {
        DataContractSerializer serializer = new DataContractSerializer(typeof(Library));
        using (StringReader stringReader = new StringReader(xmlString))
        using (XmlReader xmlReader = XmlReader.Create(stringReader))
        {
            return (Library)serializer.ReadObject(xmlReader);
        }
    }
}

Deserialized Library: Central Library Deserialization failed: Object reference not set to an instance of an object Stack trace: at Program.Main (System.String[] args) [0x0002a] in :0

I keep running into this issue and I am not sure why. I have read manual after manual and no fix. How do I get this to correctly serialize this list of books?

Note, I have looked at WCF: Serializing and Deserializing generic collections but none of the proposed changes addressed the issue. There's only one fix that addresses this, which is to move one element in the XML lower, to this:

<?xml version=""1.0"" encoding=""utf-8""?>
<Library
    xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
    <Books>
        <Book>
            <Title>The Great Gatsby</Title>
            <Author>F. Scott Fitzgerald</Author>
            <Year>1925</Year>
        </Book>
        <Book>
            <Title>To Kill a Mockingbird</Title>
            <Author>Harper Lee</Author>
            <Year>1960</Year>
        </Book>
    </Books>
    <Name>Central Library</Name>
</Library>

This makes no sense as ordering is not mentioned anywhere in Microsoft's own documentation.

It should print the list of books

Upvotes: 0

Views: 60

Answers (1)

Sergey A Kryukov
Sergey A Kryukov

Reputation: 629

I fixed it with this, just for example:

public class Library {
    [DataMember(Order = 0)] public string Name { get; set; }
    [DataMember(Name = "Books", Order = 1)]
    public List<Book> books = new();
    public IList<Book> Books => books;
}

Now it reads your original XML string as it is in your code sample correctly. Note that it breaks your data contract. Alternatively, you could not break the contract, but reorder elements in your source XML.

The problem was null Books. It happened because your XML string did not correspond to your data contract. In XML, Name was the first element in Library, it would work with your unmodified data contract if you moved it after Books.

This is what it is. You can decide how to deal with ordering in each case, to avoid Order parameters or not. Anyway, I would strongly recommend that you at least create a comprehensive sample of XML data using serializer.WriteObject first. You can write the sample object graph to a file and only then write the input XML manually, if you even ever need it.

Besides, I've demonstrated the pattern when Books is never null. This is the way to correctly implement object composition or aggregation. I don't think you ever envisioned the situation when Books is null, it makes sense if it is non-null but empty or not. In your original code, you would need to create an instance of the list Books instance every time, but it makes no sense.

Also note that the public property Books is not System.Collections.Generic.List but System.Collections.Generic.IList. This is important enough. You should not make a user-facing type more concrete than it is necessary. Your use of List was a minor mistake: limiting the choice without necessity. If this is IList, you can potentially change the implementing class without breaking the contract.

Another problem with your code is not defining a namespace name for your data contracts. You should better make sure you use the same name for the same contract, otherwise, XMLs will get redundant namespaces inside the root of the object graph. So, you could do something like

static class DefinitionSet {
    internal const string dataContractNamespace = 
        "https/www.my.site.org/contracts/library";
    //...
}

[DataContract(
    Name = "Library",
    Namespace = DefinitionSet.dataContractNamespace)]
public class Library  { /* ... */ }

[DataContract(
    Name = "Book",
    Namespace = DefinitionSet.dataContractNamespace)]
public class Book { /* ... */ }

This is important for contract identity and uniqueness, maintenance, and versioning. Having an empty namespace is just fine, but not for real production.

Upvotes: 0

Related Questions