I need to serialize a class so that the serialization will include all nested value types properties.
I found it somewhat hard to generalize it in English (Not a native speaker, so an edit to the wording is welcomed), so I'll explain:
If the property is of a Nullable type: If its value is non-null, do as above (Effectively, serialize the Nullable's Value
property); else, don't serialize it.
If the property is of a class type, serialize the class' properties according to the above, and do not serialize the class name.
For example, this:
public class SerializeMe
{
public int A { get; set; }
public int? B { get; set; }
public int? C { get; set; }
public MyClass MyClass { get; set;}
}
public class MyClass
{
public int Z { get; set;}
}
If instantiated like this:
public static void Main()
{
var instance = new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2},
});
}
Should be serialized like this:
<SerializeMe>
<A>1</A>
<B>3</B>
<Z>2</Z>
</SerializeMe>
But I don't know how to do the last bullet, and I end with:
<SerializeMe>
<A>1</A>
<B>3</B>
<UndesiredTag><Z>2</Z></UndesiredTag>
</SerializeMe>
Now, the last bullet requirement invites recursion, but as I understand from this answer, it's the parent class' WriteXml
that may be able to omit the <UndesiredTag>
tag, while the nested class can't.
So, what I currently have (fiddle):
using System;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.IO;
public class SerializeMe : IXmlSerializable
{
public int A { get; set; }
public int? B { get; set; }
public int? C { get; set; }
public MyClass MyClass { get; set;}
public void WriteXml(XmlWriter writer)
{
Program.WriteXml<SerializeMe>(writer, this);
}
public void ReadXml(XmlReader reader) {}
public XmlSchema GetSchema() { return null; }
}
[AttributeUsage(AttributeTargets.Class)]
public class Nested : Attribute
{}
[Nested]
public class MyClass : IXmlSerializable
{
public int Z { get; set;}
public void WriteXml(XmlWriter writer)
{
Program.WriteXml<MyClass>(writer, this);
}
public void ReadXml(XmlReader reader) {}
public XmlSchema GetSchema() { return null; }
}
public class Program
{
public static void Main()
{
var s = XmlSerialize<SerializeMe>(new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2},
});
Console.WriteLine(s);
}
public static string XmlSerialize<T>(T entity) where T : class
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
XmlSerializer xsSubmit = new XmlSerializer(typeof(T));
StringWriter sw = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(sw, settings))
{
var xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
xsSubmit.Serialize(writer, entity, xmlns);
return sw.ToString();
}
}
public static void WriteXml<T>(XmlWriter writer, T obj)
{
PropertyInfo[] props = obj.GetType().GetProperties();
foreach (var prop in props)
{
var val = prop.GetValue(obj);
if (val != null)
{
if (prop.PropertyType.IsValueType ||
prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
writer.WriteElementString(prop.Name, val.ToString());
}
else
{
if (prop.PropertyType.GetCustomAttribute(typeof(Nested)) != null)
{
writer.WriteStartElement("UndesiredTag"); // If only I could use an empty string...
((dynamic)val).WriteXml(writer);
writer.WriteEndElement();
}
}
}
}
}
}
Note that my current code assumes only one level of nesting. If you think you can solve my issue using a recursion, it would be better - since you'd allow multiple nesting levels.
Since you override all default serialization anyway, it seems simpler to me to just ditch XmlSerializer and do it completely on your own.
public static void Main()
{
var s = XmlSerialize(new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2 },
});
Console.WriteLine(s);
}
public static string XmlSerialize(object entity)
{
var buf = new StringBuilder();
using (var writer = XmlWriter.Create(buf, new XmlWriterSettings() {
OmitXmlDeclaration = true,
Indent = true
}))
{
WriteElement(writer, entity);
}
return buf.ToString();
}
static void WriteElement(XmlWriter writer, object obj)
{
writer.WriteStartElement(obj.GetType().Name);
WriteElementProperties(writer, obj);
writer.WriteEndElement();
}
static void WriteElementProperties(XmlWriter writer, object obj)
{
foreach (var prop in obj.GetType().GetProperties())
{
var val = prop.GetValue(obj);
if (val != null)
{
if (prop.PropertyType.IsValueType ||
prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
writer.WriteElementString(prop.Name, val.ToString());
}
else
{
if (prop.PropertyType.GetCustomAttribute(typeof(Nested)) != null)
{
WriteElementProperties(writer, val);
} else {
WriteElement(writer, val);
}
}
}
}
}