views:

44

answers:

1

I've got a class that when serialized to XML looks like this (generalized for simplicity):

<root>
  <resources>
    <resource name="foo" anotherattribute="value">data</resource>
    <resource name="bar" anotherattribute="value">more data</resource>
  </resource>
  <myobject name="objName">
    <resource name="foo" />
  </myobject>
</root>

When it's deserialized, I need the instance of resource referenced in the property of the myobject instance to be the same object created during the deserialization of the resources collection. Also, if possible I don't want to have to output the full serialization of the resource instance in myobject, only the name.

Is there any way of doing this? Right now I'm considering resorting to having a separate string property for serialization purposes that gets the relevant object from root when the deserializer sets the property, but that means giving myobject a reference to the root that contains it, and I was hoping to avoid that coupling.

+1  A: 

You can't do that with XmlSerializer, because it doesn't handle object references.

If you don't have any constraint on the generated schema, you could use DataContractSerializer, which also serializes to XML but supports references. To use DataContractSerializer, each type must have a DataContract attribute, and each member you want to serialize must have a DataMember attribute :

[DataContract(Name = "root")]
public class root
{
    [DataMember]
    public List<resource> resources { get; set; }
    [DataMember]
    public myobject myobject { get; set; }
}

[DataContract]
public class myobject
{
    [DataMember]
    public string name { get; set; }
    [DataMember]
    public resource resource { get; set; }
}

[DataContract(Name = "resource", IsReference = true)]
public class resource
{
    [DataMember]
    public string name { get; set; }
    [DataMember]
    public string anotherattribute { get; set; }
    [DataMember]
    public string content { get; set; }
}

...

var serializer = new DataContractSerializer(typeof(root));
using (var xwriter = XmlWriter.Create(fileName))
{
    serializer.WriteObject(xwriter, r);
}

Note the IsReference = true for the resource class: that's what makes the serializer handle this class by reference. In the generated XML, each resource instance is only serialized once:

<?xml version="1.0" encoding="utf-8"?>
<root xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/"&gt;
  <myobject>
    <name>objName</name>
    <resource z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"&gt;
      <anotherattribute>value</anotherattribute>
      <content>data</content>
      <name>foo</name>
    </resource>
  </myobject>
  <resources>
    <resource z:Ref="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" />
    <resource z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"&gt;
      <anotherattribute>value</anotherattribute>
      <content>more data</content>
      <name>bar</name>
    </resource>
  </resources>
</root>
Thomas Levesque
The problem here is that the full deserialization of `resource` objects should be within the `resources` tag, never within a `myobject` tag. I suspect this is probably easy to sort simply by making sure that `resources` is serialized first, however. Also, I can't have extra attributes for Id and Ref in it, it's already got the name to use as a reference.
Flynn1179
Indeed, you can change the order in which the properties are serialized. And like I said, this solution only works if you don't care about the XML schema... If you want it to be exactly as you described in your question, I think you will have to handle the serialization manually by implementing IXmlSerializable
Thomas Levesque