lundi 9 avril 2018

XmlSerializer that serializes all and only nested value types properties

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 value type - serialize its name and value
  • If the property is of a Nullable type: If its value is non-null, do as above (Essentialy, 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.





Aucun commentaire:

Enregistrer un commentaire