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?
9 Answers
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>
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).
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
}
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>
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
}
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;
}
}
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;
}
}
}
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);
}
}
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));