1

I am successfully using XML serializer to serialize a Microsoft.VisualStudio.Services.Common.SerializableDictionary<T1, T2>.

Now I want to add a guid string to SerializableDictionary and serialize it as well. I therefore created the following class:

public class SerializableDictWithGuid<T1, T2> : SerializableDictionary<T1, T2>, IMyInterface
{
    [XmlElement(ElementName = idXml001)]
    public string Guid { get; set; } = string.Empty;
}

When serializing SerializableDictWithGuid with the below code, the guid string is missing in the XML output.

public class XmlSerializeHelper<T> where T : class
{
    public static string Serialize(T obj)
    {
        try
        {
            System.Xml.Serialization.XmlSerializer xsSubmit = new System.Xml.Serialization.XmlSerializer(typeof(T));

            using (var sww = new StringWriter())
            {
                using (XmlTextWriter writer = new XmlTextWriter(sww) { Formatting = Formatting.Indented })
                {
                    xsSubmit.Serialize(writer, obj);
                }

                return sww.ToString();
            }
        }
        catch (Exception ex)
        {
            logger.log(ex);
        }

        return string.Empty;
    }
}

Minimal reproducible example

SerializableDictWithGuid <string, string> test = new SerializableDictWithGuid <string, string>() { Guid = "123" };
XmlSerializeHelper<SerializableDictWithGuid <string, string>>.Serialize(test);

Question: how can I add properties to SerializableDictionary and include them in XML serialization?

dbc
  • 104,963
  • 20
  • 228
  • 340
chriscode
  • 121
  • 1
  • 8
  • 1
    I can't find any docs for `Microsoft.VisualStudio.Services.Common.SerializableDictionary`, but since `XmlSerializer` doesn't support dictionaries, `SerializableDictionary` likely works by implementing [`IXmlSerializable`](https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable) -- i.e., it serializes itself manually using handcrafted code. Most likely that code just isn't written to handle properties of subclasses. But do you have a doc or reference source link to doublecheck? – dbc Feb 05 '23 at 16:11
  • Unfortunately I also can't find any documentation. I think you are right about IXmlSerializable. Can you suggest another approach to serialize a dictionary including my guid property? Thanks – chriscode Feb 05 '23 at 16:55
  • You could re-implement serialization from scratch (though I'm sure you don't want to do that!). Or you could modify your data model so that the Guid is a property of a container class, not the dictionary itself. Other workarounds depend on how `SerializableDictionary<,>` implemented `IXmlSerializable`. Did it do so explicitly? Are the methods virtual? – dbc Feb 05 '23 at 17:06
  • I will likely add the guid to the value objects (e.g. MyClass) of the dictionary, is that what you refer to as container class? I can check for info about how IXmlSerializable is implemented, but I guess the most straightforward way is to add the property to the values of the dictionary. Do you want to add your comment as an answer? Thanks – chriscode Feb 05 '23 at 17:21
  • Are you willing to serialize the Guid as an attribute rather than an element? If so, looks like `SerializableDictionary` – dbc Feb 05 '23 at 17:26
  • My knowledge about xml terminology is quite limited tbh, but Attribute will also work as long as I can read it from the deserialized object. – chriscode Feb 05 '23 at 17:39
  • Attributes are appropriate for primitive values like strings, integers, Guids and the like. The are inappropriate for complex values with multiple attributes. See [XML attribute vs XML element](https://stackoverflow.com/q/33746). – dbc Feb 05 '23 at 17:45

1 Answers1

1

XmlSerializer does not support dictionaries, so SerializableDictionary<T1, T2> works by implementing IXmlSerializable. I.e., it serializes itself manually using handcrafted code. As such, that code knows nothing about the properties of subclasses, and doesn't have any code to discover and serialize them automatically.

Luckily it appears that IXmlSerializable.ReadXml() and IXmlSerializable.WriteXml() were not implemented explicitly, so it should be possible to re-implement them via interface re-implementation and still call the base class methods. And, while in general it's tricky to implement IXmlSerializable, if you make your Guid be an XML attribute rather than an element, implementation becomes simple because, while the design of IXmlSerializable doesn't allow for derived classes to inject child elements, it does allow for derived classes to inject custom attributes:

public class SerializableDictWithGuid<T1, T2> : SerializableDictionary<T1, T2>, IMyInterface, IXmlSerializable // Re-implement IXmlSerializable
{
    const string idXml001 = "idXml001";
    
    public string Guid { get; set; } = string.Empty;
    
    public new void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();
        // At this point the reader is positioned on the beginning of the container element for the dictionary, so we can get any custom attributes we wrote.
        Guid = reader.GetAttribute(idXml001);
        base.ReadXml(reader);
    }
    
    public new void WriteXml(XmlWriter writer)
    {
        // At this point the container element has been written but nothing else, so it's still possible to add some attributes.
        if (Guid != null)
            writer.WriteAttributeString(idXml001, Guid);
        base.WriteXml(writer);
    }
}

This results in XML that looks like:

<SerializableDictWithGuidOfStringString idXml001="123">
  <item>
    <key>
      <string>hello</string>
    </key>
    <value>
      <string>there</string>
    </value>
  </item>
</SerializableDictWithGuidOfStringString>

The XML Standard states that

Attributes are used to associate name-value pairs with elements.

In general attributes are appropriate for fixed sets of simple, primitive values such as names and IDs. Complex values with multiple internal properties, or values that repeat (i.e. collections) should be child elements, not attributes. Since an identifying Guid is a primitive value, using an attribute is appropriate. See:

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340