Reputation: 13
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
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