datavinci
datavinci

Reputation: 807

How do I serialize a class with properties declared as interfaces?

Here is my list of classes:-

public interface IUniquelyIdentifiable
    {
        string AuthorName { get; set; }
    }
    public interface IUniquelyIdentifiable1
    {
        string CategoryName { get; set; }

    }
    public interface IUniquelyIdentifiable2
    {
        string PublisherName { get; set; }
    }

    [Serializable]
    public class Book
    {
        //BookId, Category, Title, Author, Publisher, Description, Price, ISBN, PublicationDate.
        public IUniquelyIdentifiable Author { get; set; }
        public IUniquelyIdentifiable1 Category { get; set; }
        public IUniquelyIdentifiable2 Publisher { get; set; }
        public int BookId { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public int ISBN { get; set; }
        public int Price { get; set; }
        public string PublicationDate { get; set; }
    }

    [Serializable]
    class Author : IUniquelyIdentifiable
    {
        //AuthorId, AuthorName, DateOfBirth, State, City, Phone
        public int AuthorId { get; set; }
        public string AuthorName { get; set; }
        public string DateOfBirth { get; set; }
        public string State { get; set; }
        public string City { get; set; }
        public int Phone { get; set; }
    }

    [Serializable]
    class Category : IUniquelyIdentifiable1
    {
        //CategoryId, CategoryName, Description
        public int CategoryId { get; set; }
        public string CategoryName { get; set; }
        public string Description { get; set; }
    }

    [Serializable]
    class Publisher : IUniquelyIdentifiable2
    {
        //PublisherId, PublisherName, DateOfBirth, State, City, Phone.
        public int PublisherId { get; set; }
        public string PublisherName { get; set; }
        public string DateOfBirth { get; set; }
        public string State { get; set; }
        public string City { get; set; }
        public int Phone { get; set; }
    }

below is the method which is trying to serialize the objects created of the above classes:-

public static void XmlSerializeMyObject()
        {

            XmlSerializer writer = new XmlSerializer(typeof(Book));
            //overview.title = "Serialization Overview";
            var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "//SerializationOverview.xml";
            FileStream file = File.Create(path);
            writer.Serialize(file,bookList);
            file.Close();
        } 

As you can see I even used the attribute [Serializable] but still getting the error that I cant serialize interfaces.

Also I just want serialize the objects of the given classes and not interfaces.

Upvotes: 0

Views: 300

Answers (2)

Scott Hannen
Scott Hannen

Reputation: 29222

See the comments at the end. My first solution answers the question directly but I don't recommend doing it unless you have no choice. Short version - I recommend solving the problem by using the concrete types Author, Category, and Publisher instead of interfaces in the Book class.


In order to serialize a type there must be some way of determining what the concrete types of the members are. It's possible that something could serialize an instance of Book using an implementation of IUniquelyIdentifiable that is unknown to the application deserializing it.

You can modify your Book class like this:

[Serializable][DataContract][KnownType(typeof(Author))]
[KnownType(typeof(Category))]
[KnownType(typeof(Publisher))]
public class Book
{
    [DataMember]public IUniquelyIdentifiable Author { get; set; }
    [DataMember]public IUniquelyIdentifiable1 Category { get; set; }
    [DataMember]public IUniquelyIdentifiable2 Publisher { get; set; }
    [DataMember]public int BookId { get; set; }
    [DataMember]public string Title { get; set; }
    [DataMember]public string Description { get; set; }
    [DataMember]public int ISBN { get; set; }
    [DataMember]public int Price { get; set; }
    [DataMember]public string PublicationDate { get; set; }
}

Then use a DataContractSerializer to serialize. Here's an example:

using (var sw = new StringWriter())
{
    using (var xw = new XmlTextWriter(sw))
    {
        var book = new Book();
        book.Author = new Author { AuthorName = "Bob" };
        book.Category = new Category { CategoryId = 5 };
        book.Publisher = new Publisher { City = "Clearwater" };
        var serializer = new DataContractSerializer(typeof(Book));
        serializer.WriteObject(xw, book);
        var output = sw.ToString();
        Assert.IsNotNull(sw);
    }
}

This answers the question, but it doesn't solve any problems. In fact, it creates a new problem.

If you just declare the Author, Category, and Publisher properties of Book as concrete types, then you're constrained to using those types. The compiler will show an error if you try to set that property using any class that isn't Author.

But if you add the KnownType attribute as above, the problem is even worse because it's hidden. Now you can set Author to anything implementing IUniquelyIdentifiable. But when you do that (perhaps in some other part of your application) you have no way of knowing that it will fail when serialized. The constraint is still there - you still have to use Author. The difference is that now you get a runtime exception instead of a compile error.

You could instead specify a list of known types to the DataContractSerializer. That gives you a way to specify more types, even using reflection to get a list of types that implement the interface.

But it's still problematic. It's a hidden constraint. You're saying that the type of the property is IUniquelyIdentifiable. According to proper OOP design and the Liskov substitution principle you should be able to use any implementation of that interface. But in reality you can't use any implementation. You have to use one that may or may not be labeled as a "known" type somewhere else (or in multiple places) in your code. Someone could break your application at any time without causing a compile error.

Based on that I'd say to only use the above methods if you have no choice, like if you have to serialize something that you didn't design. If you're writing your own classes then I would just declare Book using the concrete types Author, Category, and Publisher.

Upvotes: 2

MichaelThePotato
MichaelThePotato

Reputation: 1525

You can't serialize interfaces. It doesn't work. your solution is to change Book's properties to the actual serializable classes:

[Serializable]
public class Book
{
    //BookId, Category, Title, Author, Publisher, Description, Price, ISBN, PublicationDate.
    public Author Author { get; set; }
    public Category Category { get; set; }
    public Publisher Publisher { get; set; }
    public int BookId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int ISBN { get; set; }
    public int Price { get; set; }
    public string PublicationDate { get; set; }
}

The question @Richard_Everett linked contain the same answer. Sorry I can't provide any better solution.

Upvotes: 1

Related Questions