2010-03-08 10 views

Respuesta

23

No se puede lograr utilizando XmlSerializer ni DataContractSerializer. Se puede hacer mediante un código escrito manualmente, como se demostró en below (no puedo comentar si el código es lo suficientemente completo para manejar todos los tipos, pero es un muy buen comienzo).

+3

Eso no significa que no pueda sin embargo, hazlo con una clase de terceros. –

+0

¿es posible escribir algo genérico? – Radu

+0

@Radu: No sé a qué te refieres con "escribir algo genérico". No si estás hablando sobre usar el Serializador XML. La respuesta de "Matthew Whited" le muestra cómo hacerlo sin usar el Serializador XML. –

66

Algo como esto debería empezar ...

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

... XML resultante ...

<object> 
    <Hello>World</Hello> 
    <Other> 
    <My>Object</My> 
    <V>1</V> 
    <B>2</B> 
    </Other> 
</object> 
+0

Supongo que 'Type.IsValueType' podría ser un buen atajo para la mayoría de los' IsAssignableFrom's. Sin embargo, no atrapa la 'cadena'. – sunside

+0

'IsValueType' puede ser una elección incorrecta. Esto usaría el valor ToString para convertir el tipo. Pero hice un cambio que debería ser mucho más fácil de entender. –

+3

Oye, tu código funciona como un hechizo. Hice algunos cambios menores, para que admita matrices. Escribí sobre esto aquí: http://martinnormark.com/serialize-c-dynamic-and-anonymous-types-to-xml – MartinHN

23

Gracias, excelente trabajo y @Matthew @ Martin.

He hecho algunas modificaciones para acomodar Nullables y Enums. También lo he cambiado para que los elementos de la matriz se nombren de acuerdo con el nombre de la propiedad + índice.

Este es el código si alguien está interesado

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 
} 
+0

Este es un buen código (+1 ya), pero no maneja las Listas (La lista debe convertirse en Arrays, .ToArray(), eso no siempre es posible). – rufo

3

Mi propia versión de la continuación excelente trabajo y @Matthew @ Martin: Las matrices de las enumeraciones son ahora soportados y la noción de matrices en generalizada en IEnumerable con el fin de también es compatible con todo tipo de colecciones.

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

La respuesta por debajo de las manijas IEnumerables en la forma en que necesitaba y se convertirán esto:

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

en esto:

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

Así que aquí tienes, otra variante de la respuesta de Mateo :

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