Can I serialize Anonymous Types as xml?

.NetXmlSerializationAnonymous Types

.Net Problem Overview


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?

.Net Solutions


Solution 1 - .Net

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>

Solution 2 - .Net

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).

Solution 3 - .Net

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
}

Solution 4 - .Net

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>

Solution 5 - .Net

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
}

Solution 6 - .Net

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;
	}
}

Solution 7 - .Net

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;
    }

}
}

Solution 8 - .Net

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 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)
    {
        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, XNamespace ns = null)
    {
        return _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);
        XElement 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;
                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), 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)
    {
        var name = XmlConvert.EncodeName(info.Name);
        var arrayCount = input == null ? 0 : input.GetLength(0);
        var elementType = input.GetType().GetElementType();
        var 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++)
        {
            var val = input.GetValue(i);

            if (isAnyType)
            {
                var valType = val.GetType();
                var xmlType = valType.GetCustomAttribute<XmlTypeAttribute>(true);
                XNamespace 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
                var 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 var 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)
    {
        return type.IsPrimitive || WriteTypes.Contains(type);
    }
}

Solution 9 - .Net

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));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRaduView Question on Stackoverflow
Solution 1 - .NetMatthew WhitedView Answer on Stackoverflow
Solution 2 - .NetJohn SaundersView Answer on Stackoverflow
Solution 3 - .NetricardoView Answer on Stackoverflow
Solution 4 - .NetPavelView Answer on Stackoverflow
Solution 5 - .NetVdesmedTView Answer on Stackoverflow
Solution 6 - .NetJeremy CookView Answer on Stackoverflow
Solution 7 - .NetGilView Answer on Stackoverflow
Solution 8 - .NetDouglas MarttinenView Answer on Stackoverflow
Solution 9 - .NetrandomoriginalityView Answer on Stackoverflow