.net XML Serialization - Storing Reference instead of Object Copy

Palani picture Palani · Oct 24, 2009 · Viewed 18.8k times · Source
  • In .Net/C# Application, I have data structures which have references to each other.
  • When I serialize them, .Net Serializes all references with separate object copies.
  • In Following Example, I am trying to serialize to Array of 'Person'
  • A 'Person' may have reference to another person.

    public class Person
    {
        public string Name;
        public Person Friend;
    }
    
    Person p1 = new Person();
    p1.Name = "John";
    
    Person p2 = new Person();
    p2.Name = "Mike";
    
    p1.Friend = p2;
    
    Person[] group = new Person[] { p1, p2 };
    XmlSerializer ser = new XmlSerializer(typeof(Person[]));
    using (TextWriter tw = new StreamWriter("test.xml"))
        ser.Serialize(tw,group );
    
    //above code generates following xml
    
    <ArrayOfPerson>
      <Person>
        <Name>John</Name>
        <Friend>
          <Name>Mike</Name>
        </Friend>
      </Person>
      <Person>
        <Name>Mike</Name>
      </Person>
    </ArrayOfPerson>
    
  • In above code, the same 'Mike' object are there on two places, since there are two references for the same object.

  • While deserializing, they become two different objects, which is not exact state when they are serialized.
  • I want to avoid this and have only copy of object in serialized xml, and all references should refer to this copy. After deserialization , i want to get back, same old data structure.
  • Is it Possible ?

Answer

Darin Dimitrov picture Darin Dimitrov · Oct 24, 2009

It is not possible with XmlSerializer. You could achieve this with DataContractSerializer using the PreserveObjectReferences property. You may take a look at this post which explains the details.

Here's a sample code:

public class Person
{
    public string Name;
    public Person Friend;
}

class Program
{
    static void Main(string[] args)
    {
        Person p1 = new Person();
        p1.Name = "John";

        Person p2 = new Person();
        p2.Name = "Mike";
        p1.Friend = p2;
        Person[] group = new Person[] { p1, p2 };

        var serializer = new DataContractSerializer(group.GetType(), null, 
            0x7FFF /*maxItemsInObjectGraph*/, 
            false /*ignoreExtensionDataObject*/, 
            true /*preserveObjectReferences : this is where the magic happens */, 
            null /*dataContractSurrogate*/);
        serializer.WriteObject(Console.OpenStandardOutput(), group);
    }
}

This produces the following XML:

<ArrayOfPerson z:Id="1" z:Size="2" xmlns="http://schemas.datacontract.org/2004/07/ToDelete" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Person z:Id="2">
        <Friend z:Id="3">
            <Friend i:nil="true"/>
            <Name z:Id="4">Mike</Name>
        </Friend>
        <Name z:Id="5">John</Name>
    </Person>
    <Person z:Ref="3" i:nil="true"/>
</ArrayOfPerson>

Now set PreserveObjectReferences to false in the constructor and you will get this:

<ArrayOfPerson xmlns="http://schemas.datacontract.org/2004/07/ToDelete" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Person>
        <Friend>
            <Friend i:nil="true"/>
            <Name>Mike</Name>
        </Friend>
        <Name>John</Name>
    </Person>
    <Person>
        <Friend i:nil="true"/>
        <Name>Mike</Name>
    </Person>
</ArrayOfPerson>

It is worth mentioning that the XML produced this way is not interoperable and can only be deserialized with a DataContractSerializer (same remark as with the BinaryFormatter).