How to serialize property of type Object with XmlSerializer

13,081

Solution 1

I don't recommend this, but yes, you can use [XmlElement] etc to tell it about multiple candidate types for a member:

public class Test
{
    private static void Main()
    {
        var ser = new XmlSerializer(typeof (Test));
        var obj = new Test {Value = "abc"};
        ser.Serialize(Console.Out, obj);
        obj = new Test { Value = 123 };
        ser.Serialize(Console.Out, obj);
        obj = new Test { Value = 456.7F };
        ser.Serialize(Console.Out, obj);
    }

    [XmlElement("a", Type = typeof(int))]
    [XmlElement("b", Type = typeof(string))]
    [XmlElement("c", Type = typeof(float))]
    public object Value { get; set; }
}

The important bits of the output (ignoring all the xmlns / <?xml> etc) are:

<Test>
  <b>abc</b>
</Test>

<Test>
  <a>123</a>
</Test>

<Test>
  <c>456.7</c>
</Test>

Solution 2

I did it implementing the IXmlSerializable interface, writing the object type as an element attribute.

  public void ReadXml(XmlReader reader)
  {
     reader.MoveToContent();

     Boolean isEmptyElement = reader.IsEmptyElement; 
     reader.ReadStartElement();
     if (!isEmptyElement)
     {

        // ...here comes all other properties deserialization

        object tag;
        if (ReadXmlObjectProperty(reader, "Tag", out tag))
        {
           Tag = tag;
        }
        reader.ReadEndElement();
     }
  }

  public void WriteXml(XmlWriter writer)
  {

     // ...here comes all other properties serialization

     WriteXmlObjectProperty(writer, "Tag", Tag);
  }

  public static bool ReadXmlObjectProperty(XmlReader reader, 
                                           string name,
                                           out object value)
  {
     value = null;

     // Moves to the element
     while (!reader.IsStartElement(name))
     {
        return false;
     }
     // Get the serialized type
     string typeName = reader.GetAttribute("Type");

     Boolean isEmptyElement = reader.IsEmptyElement; 
     reader.ReadStartElement();
     if (!isEmptyElement)
     {
        Type type = Type.GetType(typeName);

        if (type != null)
        {
           // Deserialize it
           XmlSerializer serializer = new XmlSerializer(type);
           value = serializer.Deserialize(reader);
        }
        else
        {
           // Type not found within this namespace: get the raw string!
           string xmlTypeName = typeName.Substring(typeName.LastIndexOf('.')+1);
           value = reader.ReadElementString(xmlTypeName);
        }
        reader.ReadEndElement();
     }

     return true;
  }
  public static void WriteXmlObjectProperty(XmlWriter writer, 
                                            string name,
                                            object value)
  {
     if (value != null)
     {
        Type valueType = value.GetType();
        writer.WriteStartElement(name);
        writer.WriteAttributeString("Type", valueType.FullName);
        writer.WriteRaw(ToXmlString(value, valueType));
        writer.WriteFullEndElement();
     }
  }

  public static string ToXmlString(object item, Type type) 
  {
     XmlWriterSettings settings = new XmlWriterSettings();
     settings.Encoding = Encoding.ASCII;
     settings.Indent = true;
     settings.OmitXmlDeclaration = true;
     settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;

     using(StringWriter textWriter = new StringWriter()) 
     using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) 
     {
        XmlSerializer serializer = new XmlSerializer(type);
        serializer.Serialize(xmlWriter, item, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
        return textWriter.ToString();
     }
  }

Note: in the code I use no namespace and ASCII encoding, those are non mandatory choices.

HTH, Cabbi

Share:
13,081
loczek
Author by

loczek

Updated on June 07, 2022

Comments

  • loczek
    loczek about 2 years

    I have a property:

    public object Tag
    

    but it can contain finite number of types, unfortunately without base type (except object type). But when I serialize the object with this property, it doesn't get serialized. Is there a way to instruct XmlSerializer with possible types?

  • Evan
    Evan about 7 years
    This only works if you use different names or namespaces for the elements though (a,b,c in this example). In my case, I needed the element name to always be the same, so I posted an answer that seemed to work for me.
  • Etherman
    Etherman over 5 years
    This does not seem to work with simple types like bool/int/float/string