XML Serialization Reference - Duplicate

[Java or C #] I have a serialization problem. How not to duplicate all information about an object and use only the link?

Examples of classes:

class Author {
  public String id;
  public String name;
}

class Book {
  public String id;
  public Author author;
  public String title;
}

      

And I need to format the output file like here:

<store>
  <authors>
   <author id="PK">
     <name>Philip Kindred</name>
    </author>
  </authors>

  <books>
    <book id="u1">
      <author>PK</author> <!--  use only ID -->
      <title>Ubik</title>
    </book>
  </books>
</store>

      

+3


source to share


2 answers


You are facing the problem of representing an aggregation instead of a composition relation in XML. XML serialization is extremely easy when you preserve the parent-child relationship when the parent owns the child (i.e. Composition). In this case, the book has one (or more) author, but does not own it, because one author can be the author of many other books.

In this case, you can do something similar to what is done in databases, that is, have two separate records and express the relationship through a foreign key. See example below:

[Serializable]
public class Author
{
    [XmlAttribute("id")]
    public String Id { get; set; }
    [XmlElement("name")]
    public String Name { get; set; }
}

[Serializable]
public class Book
{
    private Author _author;

    [XmlIgnore]
    public Author Author
    {
        get { return _author; }
        set
        {
            _author = value;
            AuthorId = _author != null ? _author.Id : null;
        }
    }

    [XmlAttribute("id")]
    public String Id { get; set; }

    [XmlElement("author")]
    public String AuthorId { get; set; }

     [XmlElement("title")]
    public String Title { get; set; }
}

[Serializable]
public class Store
{
    [XmlArray("authors")]
    [XmlArrayItem("author", Type = typeof(Author))]
    public List<Author> Authors { get; set; }

    [XmlArray("books")]
    [XmlArrayItem("book", Type = typeof(Book))]
    public List<Book> Books { get; set; }

    public Store()
    {
        Books = new List<Book>();
        Authors = new List<Author>();
    }
}


internal class Program
{
    private static void Main(string[] args)
    {
        // Create some authors
        var authors = new List<Author>
        {
            new Author{Id="PK", Name="Philip Kindred"},
            new Author{Id="WS", Name="William Shakespeare"},
        };

        // Create some books linked to the authors
        var books = new List<Book>
        {
            new Book{Id = "U1", Author = authors[0], Title = "Do Androids Dream of Electric Sheep?"},
            new Book{Id = "U2", Author = authors[1], Title = "Romeo and Juliet"}
        };

        var store = new Store {Authors = authors, Books = books};

        var success = Serialiser.SerialiseToXml(store, "store.xml");

        // Deserialize the data from XML
        var store2 = Serialiser.DeserialseFromXml<Store>("store.xml");

        // Resolve the actual Author instances from the saved IDs (foreign key equivalent in databases)
        foreach (var book in store2.Books)
            book.Author = store2.Authors.FirstOrDefault(author => author.Id == book.AuthorId);

        // Now variable 'store' and 'store2' have the same equivalent data
    }
}

// Helper class to serialize and deserialize the data to XML file
public static class Serialiser
{
    public static bool SerialiseToXml(object obj, string filename)
    {
        try
        {
            var ws = new XmlWriterSettings
            {
                NewLineHandling = NewLineHandling.Entitize,
                NewLineChars = Environment.NewLine,
                Indent = true,
                NewLineOnAttributes = false
            };
            var xs = new XmlSerializer(obj.GetType());
            using (var writer = XmlWriter.Create(filename, ws))
                xs.Serialize(writer, obj);

            return true;
        }
        catch(Exception ex)
        {
            return false;
        }
    }

    public static T DeserialseFromXml<T>(string filename) where T : new()
    {
        var typeofT = typeof(T);
        try
        {
            var xs = new XmlSerializer(typeofT);
            using (var reader = XmlReader.Create(filename))
                return (T)xs.Deserialize(reader);
        }
        catch(Exception ex)
        {
            return default(T);
        }
    }
}

      



"store.xml" will look like this:

<?xml version="1.0" encoding="utf-8"?>
<Store xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<authors>
    <author id="PK">
    <name>Philip Kindred</name>
    </author>
    <author id="WS">
    <name>William Shakespeare</name>
    </author>
</authors>
<books>
    <book id="U1">
    <author>PK</author>
    <title>Do Androids Dream of Electric Sheep?</title>
    </book>
    <book id="U2">
    <author>WS</author>
    <title>Romeo and Juliet</title>
    </book>
</books>
</Store>

      

+2


source


C # answer

You can do this if you have a larger container class that can handle cross-references between objects. In your case, you seem to have an object Store

that can serve this purpose. Store

supports dictionaries of books and authors by title; Book

remembers id

its author, and the actual one Author

is retrieved from the repository as needed. Of course, this requires that Author

and Book

know the store in which they exist.

An example implementation could be as follows:



public class Author
{
    string id;
    Store store;

    [XmlIgnore]
    public Store Store {
        get {
            return store;
        }
        set {
            if (store != null && id != null)
                store.Authors.Remove(id);
            this.store = value;
            if (store != null && id != null)
                store.Authors[id] = this;
        }
    }

    [XmlAttribute("id")]
    public string Id
    {
        get
        {
            return id;
        }
        set
        {
            if (store != null && id != null)
                store.Authors.Remove(id);
            this.id = value;
            if (store != null && id != null)
                store.Authors[id] = this;
        }
    }

    [XmlElement("name")]
    public string Name { get; set; }
}

public class Book
{
    string authorId;
    string id;
    Store store;

    [XmlIgnore]
    public Store Store
    {
        get
        {
            return store;
        }
        set
        {
            if (store != null && id != null)
                store.Books.Remove(id);
            this.store = value;
            if (store != null && id != null)
                store.Books[id] = this;
        }
    }

    [XmlAttribute("id")]
    public string Id
    {
        get
        {
            return id;
        }
        set
        {
            if (store != null && id != null)
                store.Books.Remove(id);
            this.id = value;
            if (store != null && id != null)
                store.Books[id] = this;
        }
    }

    [XmlElement("author")]
    public string AuthorID
    {
        get
        {
            return authorId;
        }
        set
        {
            authorId = value;
        }
    }

    [XmlIgnore]
    public Author Author
    {
        get
        {
            if (store == null)
                return null;
            if (AuthorID == null)
                return null;
            return store.Authors[AuthorID];
        }
        set
        {
            if (value == Author)
                return;
            if (value == null)
            {
                authorId = null;
            }
            else
            {
                if (value.Id == null)
                    throw new ArgumentException();
                authorId = value.Id;
            }

            AssertCorrectAuthor(value);
        }
    }

    [Conditional("DEBUG")]
    private void AssertCorrectAuthor(Author author)
    {
        if (store != null)
            Debug.Assert(author == Author);
    }

    [XmlElement("title")]
    public string Title { get; set; }
}

[XmlRoot("store")]
public class Store
{
    readonly Dictionary<string, Book> books = new Dictionary<string, Book>();
    readonly Dictionary<string, Author> authors = new Dictionary<string, Author>();

    [XmlIgnore]
    public IDictionary<string, Book> Books
    {
        get
        {
            return books;
        }
    }

    [XmlIgnore]
    public IDictionary<string, Author> Authors
    {
        get
        {
            return authors;
        }
    }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [XmlArray("authors")]
    [XmlArrayItem("author")]
    public Author[] AuthorList // proxy array for serialization.
    {
        get
        {
            return Authors.Values.ToArray();
        }
        set
        {
            foreach (var author in authors.Values)
            {
                author.Store = null;
            }
            Authors.Clear();
            if (value == null)
                return;
            foreach (var author in value)
            {
                author.Store = this;
            }
        }
    }

    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    [XmlArray("books")]
    [XmlArrayItem("book")]
    public Book[] BookList // proxy array for serialization.
    {
        get
        {
            return Books.Values.ToArray();
        }
        set
        {
            foreach (var book in Books.Values)
            {
                book.Store = null;
            }
            Books.Clear();
            if (value == null)
                return;
            foreach (var book in value)
            {
                book.Store = this;
            }
        }
    }
}

      

And to check:

public static class TestStore
{
    public static void Test()
    {
        string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<store>
  <authors>
   <author id=""PK"">
     <name>Philip Kindred</name>
    </author>
  </authors>

  <books>
    <book id=""u1"">
      <author>PK</author> <!--  use only ID -->
      <title>Ubik</title>
    </book>
    <book id=""t1"">
      <author>PK</author> <!--  use only ID -->
      <title>The Transmigration of Timothy Archer</title>
    </book>
  </books>
</store>
";
        var store = xml.LoadFromXML<Store>();
        Debug.Assert(store.BookList[0].Author == store.AuthorList[0]); // no assert
        Debug.Assert(store.BookList[1].Author == store.AuthorList[0]); // no assert; verify that all books use the same instance of the `Author` class.
    }
}

      

+1


source







All Articles