What is a better way saving List<T> fields using XML serialization

Junfeng Liang 156 Reputation points
2021-11-24T18:58:56.517+00:00

Here is my class:

public class Character
{
public int ID;
public string Name;
public List<Character> Friends = new List<Character>();

}

When I saved a list of characters with name A, B, C-where A has B and C as friends- XML gave me this:

<?xml version="1.0"?>
<ArrayOfCharacter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Character>
<ID>0</ID>
<Name>A</Name>
<Friends>
<Character>
<ID>1</ID>
<Name>B</Name>
<Friends />
</Character>
<Character>
<ID>1</ID>
<Name>C</Name>
<Friends />
</Character>
</Friends>
</Character>
<Character>
<ID>1</ID>
<Name>B</Name>
<Friends />
</Character>
</ArrayOfCharacter>

That means every time I have a character in the friends list, it repetavely write all the details every friend character. If I have a lot of other fields/properties and have a huge list of characters, the size of the data file will exponentially increase. Is it a way to save just the reference instead?

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,279 questions
0 comments No comments
{count} votes

Accepted answer
  1. Viorel 119.6K Reputation points
    2021-11-24T21:53:20.663+00:00

    Consider the BinaryFormatter class, however documentation recommends other tools. For example, consider the new System.Text.Json package (which can be added using "Manage NuGet Packages" command, if not included yet).

    Then try this code:

    string json = JsonSerializer.Serialize( mylist, new JsonSerializerOptions { IncludeFields = true, ReferenceHandler = ReferenceHandler.Preserve, WriteIndented = true } );  
    

    It will generate a special JSON string, where objects are not duplicated. Then you can write it to file. Or use other forms of this function.

    See some situations: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references.


3 additional answers

Sort by: Most helpful
  1. P a u l 10,751 Reputation points
    2021-11-24T19:32:29.053+00:00

    Would it be possible to make Friends a List<string> and store a collection of character's ID instead? Then after you deserialise you can just lookup friends by their ID from the overall list of characters.

    0 comments No comments

  2. Karen Payne MVP 35,551 Reputation points
    2021-11-24T19:39:02.33+00:00

    How about this or tweak to suit.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Windows.Forms;
    using System.Xml.Serialization;
    
    namespace WorkingXml
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
                Shown += OnShown;
            }
    
            private void OnShown(object sender, EventArgs e)
            {
                MockedOperations.Demo();
            }
        }
    
        public class MockedOperations
        {
            public static void Demo()
            {
                var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Test.xml");
    
                if (File.Exists(fileName))
                {
                    File.Delete(fileName);
                }
    
                Person person1 = new Person
                {
                    FriendsList = new List<Friends>
                    {
                        new Friends() { Character = "A" },
                        new Friends() { Character = "B" }
                    },
                    Id = 1,
                    Name = "Jun"
                };
    
                Person person2 = new Person
                {
                    FriendsList = new List<Friends>
                    {
                        new Friends() { Character = "A" },
                        new Friends() { Character = "B" },
                        new Friends() { Character = "C" },
                        new Friends() { Character = "D" }
                    },
                    Id = 2,
                    Name = "Anne"
                };
    
                List<Person> list = new List<Person>() { person1, person2 };
    
                FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate);
                XmlSerializer serializer = new XmlSerializer(typeof(List<Person>));
                serializer.Serialize(fs, list);
    
            }
        }
        [Serializable]
        [XmlRoot("Person")]
        public class Person
        {
            public int Id { get; set; }
            public string Name { get; set; }
            [XmlArray("FriendsList"), XmlArrayItem(typeof(Friends), ElementName = "Friends")]
            public List<Friends> FriendsList { get; set; }
        }
        [Serializable]
        public class Friends
        {
            public string Character { get; set; }
        }
    }
    

    Results

    <?xml version="1.0"?>
    <ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Person>
        <Id>1</Id>
        <Name>Jun</Name>
        <FriendsList>
          <Friends>
            <Character>A</Character>
          </Friends>
          <Friends>
            <Character>B</Character>
          </Friends>
        </FriendsList>
      </Person>
      <Person>
        <Id>2</Id>
        <Name>Anne</Name>
        <FriendsList>
          <Friends>
            <Character>A</Character>
          </Friends>
          <Friends>
            <Character>B</Character>
          </Friends>
          <Friends>
            <Character>C</Character>
          </Friends>
          <Friends>
            <Character>D</Character>
          </Friends>
        </FriendsList>
      </Person>
    </ArrayOfPerson>
    
    0 comments No comments

  3. Junfeng Liang 156 Reputation points
    2021-11-24T21:55:27.377+00:00

    Thank you very much for the quick answer.

    That seems to be another way to replace the list of objects with a list of ID references. I can definitely do that and add a method in the class to return the actual objects from the IDs. However, that means for every type I define, I need to refer with ID and then make a method to turn that back into the object.

    I wonder whether there are more "automatic" ways to implement it. I want to make my code shorter and easier to read. I did find some hint from the stackoverflow:

    https://stackoverflow.com/questions/1617528/net-xml-serialization-storing-reference-instead-of-object-copy

    However, as a beginner of C#, I am having a hard time to turn the single object example into a list/array of object example. If you can help me a bit, it will be greatly appreciated.

    Here is how I implemented with the second method:

    using ExtendedXmlSerializer;
    using ExtendedXmlSerializer.Configuration;
    using System.IO;
    
    
    
    
    IExtendedXmlSerializer serializer = new ConfigurationContainer().ConfigureType<Character>()
                                                 .EnableReferences(p => p.ID)
                                                 .Create();
                string xml = serializer.Serialize(new XmlWriterSettings { Indent = true }, characters);
    
    
    
                var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "//test.xml";
    
    
                File.WriteAllText(path, xml);
    

    It gave me this

    <?xml version="1.0" encoding="utf-8"?>
    <List xmlns:ns1="clr-namespace:Serialization;assembly=Serialization" xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:arguments="ns1:Character" xmlns="https://extendedxmlserializer.github.io/system">
      <Capacity>4</Capacity>
      <ns1:Character ID="0">
        <Name>A</Name>
        <Friends>
          <Capacity>4</Capacity>
          <ns1:Character ID="1">
            <Name>B</Name>
          </ns1:Character>
          <ns1:Character ID="2">
            <Name>C</Name>
          </ns1:Character>
        </Friends>
      </ns1:Character>
      <ns1:Character exs:entity="1" />
    </List>
    

    which does not seem like what I want because I failed to deserialize it:

    loadedCharacters = serializer.Deserialize<List<Character>>(xml);
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.