60

I understood that anonymous types are marked private by the compiler and the properties are read-only. Is there a way to serialize them to xml (without deserialize) ? It works with JSON, how can I do it with XML?

10 Answers 10

72

Something like this should get you started...

class Program
{
    static void Main(string[] args)
    {
        var me = new
        {
            Hello = "World",
            Other = new
            {
                My = "Object",
                V = 1,
                B = (byte)2
            }
        };

        var x = me.ToXml();
    }
}
public static class Tools
{
    private static readonly Type[] WriteTypes = new[] {
        typeof(string), typeof(DateTime), typeof(Enum), 
        typeof(decimal), typeof(Guid),
    };
    public static bool IsSimpleType(this Type type)
    {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    public static XElement ToXml(this object input)
    {
        return input.ToXml(null);
    }
    public static XElement ToXml(this object input, string element)
    {
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
            element = "object";
        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null)
        {
            var type = input.GetType();
            var props = type.GetProperties();

            var elements = from prop in props
                           let name = XmlConvert.EncodeName(prop.Name)
                           let val = prop.GetValue(input, null)
                           let value = prop.PropertyType.IsSimpleType()
                                ? new XElement(name, val)
                                : val.ToXml(name)
                           where value != null
                           select value;

            ret.Add(elements);
        }

        return ret;
    }
}

... resulting xml ...

<object>
  <Hello>World</Hello>
  <Other>
    <My>Object</My>
    <V>1</V>
    <B>2</B>
  </Other>
</object>
6
  • I guess Type.IsValueType could be a nice shortcut for most of the IsAssignableFrom s. Doesn't catch the string though.
    – sunside
    Commented Aug 16, 2010 at 15:55
  • IsValueType can be a wrong choice. This would use the ToString value to convert the type. But I have made a change that should be much easier to understand. Commented Aug 16, 2010 at 17:26
  • 3
    Hey - your code works like a charm. I made some minor changes, so that it supports Arrays, I wrote about it here: martinnormark.com/…
    – MartinHN
    Commented Feb 5, 2011 at 8:31
  • OK, this is a good sample... now, how can I serialize objects, that contains anonymous types... i suppose via custom serialization...
    – serhio
    Commented Oct 6, 2011 at 15:03
  • This may not be useful to many people, but is the right answer to this particular question Commented Sep 26, 2013 at 18:16
28

It can't be accomplished using XmlSerializer nor DataContractSerializer. It can be done by a manually written code, as demonstrated below (I can't comment as to whether the code is comprehensive enough to handle all types - but it's a very good start).

11
  • 4
    That doesn't mean you can't do it with a third-party class, though. Commented Mar 8, 2010 at 21:23
  • is it possible to write something generic?
    – Radu
    Commented Mar 8, 2010 at 21:32
  • @Radu: I don't know what you mean "write something generic". Not if you're talking about using the XML Serializer. The answer from "Matthew Whited" shows you how to do this without using the XML Serializer. Commented Mar 8, 2010 at 21:50
  • It's probably one of the guys that downvotes all answers that don't get marked as the "correct" answer. Shame that people can't see that you are also correct in that the built-in xml serializer will not work. Commented Mar 9, 2010 at 4:38
  • 3
    I do believe that Matthews answer below is the correct answer. I took his code and it worked out of the box - I made some changes to make it support Arrays: martinnormark.com/…
    – MartinHN
    Commented Feb 5, 2011 at 8:32
27

Thank you, excellent work @Matthew and @Martin.

I have made a couple of modification to accomodate Nullables and Enums. Also I have changed it so that array elements are named according to the name of the property + index.

Here is the code if anyone is interested

public static class ObjectExtensions {
    #region Private Fields
    private static readonly Type[] WriteTypes = new[] {
        typeof(string), typeof(DateTime), typeof(Enum), 
        typeof(decimal), typeof(Guid),
    };
    #endregion Private Fields
    #region .ToXml
    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <returns>Returns the object as it's XML representation in an XElement.</returns>
    public static XElement ToXml(this object input) {
        return input.ToXml(null);
    }

    /// <summary>
    /// Converts an anonymous type to an XElement.
    /// </summary>
    /// <param name="input">The input.</param>
    /// <param name="element">The element name.</param>
    /// <returns>Returns the object as it's XML representation in an XElement.</returns>
    public static XElement ToXml(this object input, string element) {
        return _ToXml(input, element);
    }

    private static XElement _ToXml(object input, string element, int? arrayIndex = null, string arrayName = null) {
        if (input == null)
            return null;

        if (String.IsNullOrEmpty(element)) {
            string name = input.GetType().Name;
            element = name.Contains("AnonymousType") 
                ? "Object" 
                : arrayIndex != null
                    ? arrayName + "_" + arrayIndex
                    : name;
        }

        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null) {
            var type = input.GetType();
            var props = type.GetProperties();

            var elements = props.Select(p => {
                var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
                var name = XmlConvert.EncodeName(p.Name);
                var val = pType.IsArray ? "array" : p.GetValue(input, null);
                var value = pType.IsArray 
                    ? GetArrayElement(p, (Array)p.GetValue(input, null))
                    : pType.IsSimpleType() || pType.IsEnum 
                        ? new XElement(name, val) 
                        : val.ToXml(name);
                return value;
            })
            .Where(v=>v !=null);

            ret.Add(elements);
        }

        return ret;
    }

    #region helpers
    /// <summary>
    /// Gets the array element.
    /// </summary>
    /// <param name="info">The property info.</param>
    /// <param name="input">The input object.</param>
    /// <returns>Returns an XElement with the array collection as child elements.</returns>
    private static XElement GetArrayElement(PropertyInfo info, Array input) {
        var name = XmlConvert.EncodeName(info.Name);

        XElement rootElement = new XElement(name);

        var arrayCount = input == null ? 0 : input.GetLength(0);

        for (int i = 0; i < arrayCount; i++) {
            var val = input.GetValue(i);
            XElement childElement = val.GetType().IsSimpleType() ? new XElement(name + "_" + i, val) : _ToXml(val, null, i, name);

            rootElement.Add(childElement);
        }

        return rootElement;
    }

    #region .IsSimpleType
    public static bool IsSimpleType(this Type type) {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    #endregion .IsSimpleType

    #endregion helpers
    #endregion .ToXml
}
1
  • This is a nice code (+1 already), but it doesn't handle Lists (List have to be converted to Arrays, .ToArray(), that's not always possible).
    – rufo
    Commented May 28, 2013 at 23:11
16

I know this is an old post, but my solution converts an anonymous type to XML in only 2 lines of code.

First convert you anonymous type to JSON, and then from JSON to XML.

var jsonText = JsonConvert.SerializeObject(data);           // convert to JSON
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText); // convert JSON to XML Document

Sample

var data = new       // data - Anonymous Type
{
    Request = new
    {
        OrderNumber = 123,
        Note = "Hello World"
    }
};

var jsonText = JsonConvert.SerializeObject(data);           
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText);

Console.WriteLine(doc.OuterXml);                            

Output

<Request>
    <OrderNumber>123</OrderNumber>
    <Note>Hello World</Note>
</Request>
2
  • I was looking exactly for something about that. There is an overhead to pay but it's the simplest solution so far. +1
    – T-moty
    Commented Dec 30, 2019 at 16:55
  • that works only if you have single node. to make it generic you can change to var jsonText = JsonConvert.SerializeObject(new { AAA = data} ); return JsonConvert.DeserializeXmlNode(jsonText).DocumentElement.InnerXml;
    – Sasha Bond
    Commented May 12, 2023 at 16:13
4

My own version of then excellent work @Matthew and @Martin : Arrays of enums are now supported and the notion of arrays in generalized into IEnumerable in order to also support all sort of collections.

public static class ObjectExtensions {
/// <summary>
/// Converts an anonymous type to an XElement.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>Returns the object as it's XML representation in an XElement.</returns>
public static XElement ToXml2(this object input) {
    return input.ToXml2(null);
}

/// <summary>
/// Converts an anonymous type to an XElement.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="element">The element name.</param>
/// <returns>Returns the object as it's XML representation in an XElement.</returns>
public static XElement ToXml2(this object input, string element) {
    return _ToXml(input, element);
}

private static XElement _ToXml(object input, string element, int? arrayIndex = null, string arrayName = null) {
    if (input == null)
        return null;

    if (String.IsNullOrEmpty(element)) {
        string name = input.GetType().Name;
        element = name.Contains("AnonymousType") 
            ? "Object" 
            : arrayIndex != null
                ? arrayName + "_" + arrayIndex
                : name;
    }

    element = XmlConvert.EncodeName(element);
    var ret = new XElement(element);

    if (input != null) {
        var type = input.GetType();
        var props = type.GetProperties();

        var elements = props.Select(p => {
            var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
            var name = XmlConvert.EncodeName(p.Name);
            var val = pType.IsArray ? "array" : p.GetValue(input, null);
            var value = pType.IsEnumerable()
                ? GetEnumerableElements(p, (IEnumerable)p.GetValue(input, null))
                : pType.IsSimpleType2() || pType.IsEnum 
                    ? new XElement(name, val) 
                    : val.ToXml2(name);
            return value;
        })
        .Where(v=>v !=null);

        ret.Add(elements);
    }

    return ret;
}

#region helpers

private static XElement GetEnumerableElements(PropertyInfo info, IEnumerable input) {
    var name = XmlConvert.EncodeName(info.Name);

    XElement rootElement = new XElement(name);

    int i = 0;
    foreach(var v in input)
    {
        XElement childElement = v.GetType().IsSimpleType2() || v.GetType().IsEnum ? new XElement(name + "_" + i, v) : _ToXml(v, null, i, name);
        rootElement.Add(childElement);
        i++;
    }
    return rootElement;
}

private static readonly Type[] WriteTypes = new[] {
    typeof(string), typeof(DateTime), typeof(Enum), 
    typeof(decimal), typeof(Guid),
};
public static bool IsSimpleType2(this Type type) {
    return type.IsPrimitive || WriteTypes.Contains(type);
}

private static readonly Type[] FlatternTypes = new[] {
    typeof(string)
};
public static bool IsEnumerable(this Type type) {
    return typeof(IEnumerable).IsAssignableFrom(type) && !FlatternTypes.Contains(type);
}
#endregion
}
3

The answer below handles IEnumerables in the way I needed and will turn this:

new
{
    Foo = new[]
    {
        new { Name = "One" },
        new { Name = "Two" },
    },
    Bar = new[]
    {
        new { Name = "Three" },
        new { Name = "Four" },
    },
}

into this:

<object>
    <Foo><Name>One</Name></Foo>
    <Foo><Name>Two</Name></Foo>
    <Bar><Name>Three</Name></Bar>
    <Bar><Name>Four</Name></Bar>
</object>

So here you go, yet another variant of Matthew's answer:

public static class Tools
{
    private static readonly Type[] WriteTypes = new[] {
        typeof(string),
        typeof(Enum),
        typeof(DateTime), typeof(DateTime?),
        typeof(DateTimeOffset), typeof(DateTimeOffset?),
        typeof(int), typeof(int?),
        typeof(decimal), typeof(decimal?),
        typeof(Guid), typeof(Guid?),
    };
    public static bool IsSimpleType(this Type type)
    {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    public static object ToXml(this object input)
    {
        return input.ToXml(null);
    }
    public static object ToXml(this object input, string element)
    {
        if (input == null)
            return null;

        if (string.IsNullOrEmpty(element))
            element = "object";
        element = XmlConvert.EncodeName(element);
        var ret = new XElement(element);

        if (input != null)
        {
            var type = input.GetType();

            if (input is IEnumerable && !type.IsSimpleType())
            {
                var elements = (input as IEnumerable<object>)
                    .Select(m => m.ToXml(element))
                    .ToArray();

                return elements;
            }
            else
            {
                var props = type.GetProperties();

                var elements = from prop in props
                               let name = XmlConvert.EncodeName(prop.Name)
                               let val = prop.GetValue(input, null)
                               let value = prop.PropertyType.IsSimpleType()
                                    ? new XElement(name, val)
                                    : val.ToXml(name)
                               where value != null
                               select value;

                ret.Add(elements);
            }
        }

        return ret;
    }
}
0

Another poster Pavel mentioned using Newtonsoft.Json methods for quick 2 line conversion

var jsonText = JsonConvert.SerializeObject(data);           
XmlDocument doc = JsonConvert.DeserializeXmlNode(jsonText);

I would suggest this could actually be a single line of code :)

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

XmlDocument doc = JsonConvert.DeserializeXmlNode(JsonConvert.SerializeObject(data));
1
  • try it with var data = new {A = "a", B = "b"}; :)
    – Sasha Bond
    Commented May 12, 2023 at 16:16
0

I have made a small change in Jeremy Cook's answer to allow use of Attributes, Namespaces, and how it handles lists.

Will turn this:

var sample = new
{
    versionAttribute = "v10.2",
    Foo = new
    {
        IdAttribute = "ID_50",
        Name = "Jhon",
        NickName = "Jonny",
    },
    Bar = new[]
    {
        new { Name = "Three" },
        new { Name = "Four" },
    },
};

into this:

<Customer version="v10.2" xmlns="http://www.test.org.br/customer.xsd">
  <Foo Id="ID_50">
    <Name>Jhon</Name>
    <NickName>Jonny</NickName>
  </Foo>
  <Bar>
    <Name>Three</Name>
    <Name>Four</Name>
  </Bar>
</Customer>

Here is the full code:

public static class Tools
{
    private static readonly Type[] WriteTypes = new[]
    {
        typeof(string),
        typeof(Enum),
        typeof(DateTime), typeof(DateTime?),
        typeof(DateTimeOffset), typeof(DateTimeOffset?),
        typeof(int), typeof(int?),
        typeof(decimal), typeof(decimal?),
        typeof(Guid), typeof(Guid?),
    };
    public static bool IsSimpleType(this Type type)
    {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
    public static XElement ToXml(this object input)
    {
        return input.ToXml(null);
    }
    public static XElement ToXml(this object input, XElement root)
    {
        if (input == null)
            return null;

        if (root == null)
            root = new XElement("Root");

        var ret = new XElement(root);

        if (input != null)
        {
            var type = input.GetType();

            if (input is IEnumerable && !type.IsSimpleType())
            {
                var elements = (input as IEnumerable<object>)
                    .Select(m => m.ToXml(root).FirstNode)
                    .ToArray();

                ret.Add(elements);
            }
            else
            {
                var props = type.GetProperties();

                var elements = props.Select(prop =>
                {
                    var name = XmlConvert.EncodeName(prop.Name);
                    var val = prop.GetValue(input, null);

                    XElement value = null;
                    if (prop.PropertyType.IsSimpleType())
                    {
                        if (name.EndsWith("Attribute"))
                        {
                            ret.SetAttributeValue(name.Replace("Attribute", ""), val);
                        }
                        else
                        {
                            value = new XElement(root.Name.Namespace + name, val);
                        }
                    }
                    else
                    {
                        value = val.ToXml(new XElement(root.Name.Namespace + name));
                    }

                    return value;
                }).Where(value => value != null);

                ret.Add(elements);
            }
        }

        return ret;
    }
}`

sample use:

XNamespace nsA = "http://www.test.org.br/customer.xsd";
XElement rootElement = new XElement(nsA + "Customer");
//you can add other namespaces and attributes using XElement

sample.ToXml(rootElement)
-1

My first post to contribute to a website that always always helps a lot Bellow is a full solution that can be used with .net core (tested using 3.1) just call ConvertAnonymousToType.Convert("MyNewType", target, out Type newType) The return will be an object that you can use to return inside a controller or to serialize manually (System.Xml.Serialization.XmlSerializer). Using the object as a return Inside a controller: 1. configure the service (Startup.cs > ConfigureServices > services.AddMvcCore(options => options.OutputFormatters.Add(new XmlSerializerOutputFormatter());),
2. Target Action: use [FormatFilter] and [Route(".......{format}")], 3. Return the object

If any extra anonymous type needs to compose the reply (Metadata, Resultset, etc) DynamicTypeBuilder.CreateNewObject can be used to do service :)

Thanks again

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;


namespace Configuration
{
public static class DynamicTypeBuilder
{
    public static object CreateNewObject(string typeName, Dictionary<string, Type> properties, out Type newType)
    {
        newType = CompileResultType(typeName, properties);
        return Activator.CreateInstance(newType);
    }

    public static Type CompileResultType(string typeName, Dictionary<string, Type> properties)
    {
        TypeBuilder tb = GetTypeBuilder(typeName);
        ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

        //Add properties
        properties.ToList().ForEach(p => CreateProperty(tb, p.Key, p.Value));

        //Created Type with properties
        Type objectType = tb.CreateType();
        return objectType;
    }

    //Create Type with an standard configuration
    private static TypeBuilder GetTypeBuilder(string typeName)
    {
        string assemblyName = typeName + "InternalAssembly";
        var an = new AssemblyName(assemblyName);
        AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
        TypeBuilder tb = moduleBuilder.DefineType(typeName,
                TypeAttributes.Public |
                TypeAttributes.Class |
                TypeAttributes.AutoClass |
                TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit |
                TypeAttributes.AutoLayout,
                null);
        return tb;
    }

    private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
    {
        FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

        PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
        MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        ILGenerator getIl = getPropMthdBldr.GetILGenerator();

        getIl.Emit(OpCodes.Ldarg_0);
        getIl.Emit(OpCodes.Ldfld, fieldBuilder);
        getIl.Emit(OpCodes.Ret);

        MethodBuilder setPropMthdBldr =
            tb.DefineMethod("set_" + propertyName,
              MethodAttributes.Public |
              MethodAttributes.SpecialName |
              MethodAttributes.HideBySig,
              null, new[] { propertyType });

        ILGenerator setIl = setPropMthdBldr.GetILGenerator();
        Label modifyProperty = setIl.DefineLabel();
        Label exitSet = setIl.DefineLabel();

        setIl.MarkLabel(modifyProperty);
        setIl.Emit(OpCodes.Ldarg_0);
        setIl.Emit(OpCodes.Ldarg_1);
        setIl.Emit(OpCodes.Stfld, fieldBuilder);

        setIl.Emit(OpCodes.Nop);
        setIl.MarkLabel(exitSet);
        setIl.Emit(OpCodes.Ret);

        propertyBuilder.SetGetMethod(getPropMthdBldr);
        propertyBuilder.SetSetMethod(setPropMthdBldr);
    }
}

public static class ConvertAnonymousToType
{
    public static object Convert(string typeName, object target, out Type newType)
    {
        var properties = GetProperties(target);
        newType = DynamicTypeBuilder.CompileResultType(typeName, properties);
        return Convert(newType, target);
    }

    public static object Convert(Type type, object target)
    {
        if (target.GetType().Name == typeof(List<>).Name)
        {
            var newListType = typeof(List<>).MakeGenericType(type);
            var newList = Activator.CreateInstance(newListType);
            MethodInfo addMethod = newList.GetType().GetMethod("Add");
            ((IList<object>)target).ToList().ForEach(e =>
            {
                addMethod.Invoke(newList, new object[] { ConvertObject(type, e) });
            });
            return newList;
        }
        else
        {
            return ConvertObject(type, target);
        }
    }

    private static object ConvertObject(Type type, object refObject)
    {
        Dictionary<string, Type> properties = new Dictionary<string, Type>();
        object newObject = Activator.CreateInstance(type);
        var propertiesOrg = refObject.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
        var propertiesDes = newObject.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
        propertiesOrg.ForEach(po => propertiesDes.First(pd => pd.Name == po.Name).SetValue(newObject, po.GetValue(refObject)));
        return newObject;
    }

    private static Dictionary<string, Type> GetProperties(object target)
    {
        object objectRef = target;
        if (target.GetType().Name == typeof(List<>).Name) objectRef = ((List<object>)target).ToList()[0];

        Dictionary<string, Type> properties = new Dictionary<string, Type>();
        var lstProperties = objectRef.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToList();
        lstProperties.ForEach(p => properties.Add(p.Name, p.PropertyType));
        return properties;
    }

}
}
-1

I found this page immensely useful. I'm moving from the old SOAP web references to something that is supported in .net standard. I've changed the code to be able to serialize it in a format that will will work with SOAP web services. Its rough, but it can convert object[] to <anyType /> elements, other simple arrays are serialized correctly and you can pass in a namespace or have it use the XmlTypeAttribute on your models to assign namespaces.

public static class AnonymousTypeSerializer
{
        private static readonly XNamespace _xmlInstanceNs = "http://www.w3.org/2001/XMLSchema-instance";

        private static readonly Type[] WriteTypes = new[] {
            typeof(string), typeof(DateTime), typeof(Enum),
            typeof(decimal), typeof(Guid),
        };

        private static readonly Dictionary<Type, string> SerializedTypeNames = new Dictionary<Type, string>()
        {
            { typeof(int), "int" },
            { typeof(long), "long" }
        };

        /// <summary>
        /// Converts an anonymous type to an XElement.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <returns>Returns the object as it's XML representation in an XElement.</returns>
        public static XElement? ToXml(this object? input) => input.ToXml(null);

        /// <summary>
        /// Converts an anonymous type to an XElement.
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="element">The element name.</param>
        /// <returns>Returns the object as it's XML representation in an XElement.</returns>
        public static XElement? ToXml(this object? input, string? element, XNamespace? ns = null) => _ToXml(input, element, ns ?? XNamespace.None);

        private static XElement? _ToXml(object? input, string? element, XNamespace? ns = null, int? arrayIndex = null, string? arrayName = null)
        {
            if (input == null)
                return null;

            if (string.IsNullOrEmpty(element))
            {
                string name = input.GetType().Name;
                element = name.Contains("AnonymousType")
                    ? "Object"
                    : arrayIndex != null
                        ? $"{arrayName}"
                        : name;
            }

            element = XmlConvert.EncodeName(element);
            var ret = new XElement(ns.GetName(element));

            if (input != null)
            {
                var type = input.GetType();
                var props = type.GetProperties();
                var xmlType = type.GetCustomAttribute<XmlTypeAttribute>(true);

                if (xmlType != null && xmlType.Namespace != null)
                    ns = xmlType.Namespace;

                ret.Add(props.Select(p =>
                {
                    var pType = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
                    string name = XmlConvert.EncodeName(p.Name);

                    object val = pType.IsArray ? "array" : p.GetValue(input, null);
                    var value = pType.IsArray
                        ? GetArrayElement(p, (Array)p.GetValue(input, null), ns)
                        : pType.IsSimpleType() || pType.IsEnum
                            ? new XElement(ns.GetName(name), val)
                            : val.ToXml(name, ns);

                    return value;
                }).Where(v => v != null));
            }

            return ret;
        }

        /// <summary>
        /// Gets the array element.
        /// </summary>
        /// <param name="info">The property info.</param>
        /// <param name="input">The input object.</param>
        /// <returns>Returns an XElement with the array collection as child elements.</returns>
        private static XElement GetArrayElement(PropertyInfo info, Array? input, XNamespace ns)
        {
            string name = XmlConvert.EncodeName(info.Name);

            if (input == null)
                return null;

            int arrayCount = input.GetLength(0);
            var elementType = input.GetType().GetElementType();
            bool isAnyType = elementType == typeof(object);

            XElement rootElement;
            if (isAnyType)
                rootElement = new XElement(ns + name, new XAttribute(XNamespace.Xmlns + "xsi", _xmlInstanceNs));
            else
                rootElement = new XElement(ns + name);

            for (int i = 0; i < arrayCount; i++)
            {
                object val = input.GetValue(i);

                if (isAnyType)
                {
                    var valType = val.GetType();
                    var xmlType = valType.GetCustomAttribute<XmlTypeAttribute>(true);
                    var valNs = ns;

                    if (xmlType != null && xmlType.Namespace != null)
                        valNs = xmlType.Namespace;

                    // Create anyType element
                    var childElement = new XElement(ns + "anyType", new XAttribute(XNamespace.Xmlns + $"p{rootElement.Elements().Count()}", valNs));

                    // Create type attribute
                    string nsPrefix = childElement.GetPrefixOfNamespace(valNs);
                    childElement.Add(new XAttribute(_xmlInstanceNs + "type",
                        !string.IsNullOrEmpty(nsPrefix)
                            ? $"{nsPrefix}:{valType.Name}"
                            : valType.Name));

                    // Create child elements
                    var inner = _ToXml(val, null, ns, i, "anyType");
                    childElement.Add(inner!.Elements());

                    // Done
                    rootElement.Add(childElement);
                }
                else
                {
                    if (!SerializedTypeNames.TryGetValue(elementType, out string typeName))
                        typeName = elementType.Name;

                    rootElement.Add(elementType.IsSimpleType()
                        ? new XElement(ns.GetName(typeName.ToLower()), val)
                        : _ToXml(val, null, ns, i, typeName));
                }
            }

            return rootElement;
        }

        public static bool IsSimpleType(this Type type) => 
            type.IsPrimitive || WriteTypes.Contains(type);
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.