2012-02-21 6 views
12

Estoy tratando de serializar un objeto con varias propiedades, pero no quiero incluir todas las propiedades en la serialización. Además, me gustaría cambiar el formato de fecha.Excluyendo algunas propiedades durante la serialización sin cambiar la clase original

Por supuesto que podría agregar [XmlIgnore], pero no puedo cambiar la clase original.

La única opción que se me ocurrió fue crear una nueva clase y copiar todo el contenido entre las dos clases. Pero eso sería feo y requeriría mucho código manual.

¿Sería posible tal vez crear una subclase, ya que el original no es abstracto?

Mi pregunta es la siguiente:

  1. ¿Cómo puedo excluir algunas propiedades sin cambiar la clase original?

  2. ¿Cómo puedo personalizar el formato de fecha del XML de salida?

Requisitos:

  1. Como tipos fuertes como sea posible

  2. XML serializado deben ser deserializable

Gracias de antemano.

+0

Por cierto, estamos utilizando .NET 4.0 – Schiavini

+0

Y el XmlSerializer – Schiavini

Respuesta

11

Para quien esté interesado, decidí usar XmlAttributeOverrides, pero los hizo más fuerte mecanografiado (odio a escribir los nombres de propiedades como cadenas). Aquí está el método de extensión que utilicé para ello:

public static void Add<T>(this XmlAttributeOverrides overrides, Expression<Func<T, dynamic>> propertySelector, XmlAttributes attributes) 
    { 
     overrides.Add(typeof(T), propertySelector.BuildString(), attributes); 
    } 

    public static string BuildString(this Expression propertySelector) 
    { 
     switch (propertySelector.NodeType) 
     { 
      case ExpressionType.Lambda: 
       LambdaExpression lambdaExpression = (LambdaExpression)propertySelector; 
       return BuildString(lambdaExpression.Body); 

      case ExpressionType.Convert: 
      case ExpressionType.Quote: 
       UnaryExpression unaryExpression = (UnaryExpression)propertySelector; 
       return BuildString(unaryExpression.Operand); 

      case ExpressionType.MemberAccess: 

       MemberExpression memberExpression = (MemberExpression)propertySelector; 
       MemberInfo propertyInfo = memberExpression.Member; 

       if (memberExpression.Expression is ParameterExpression) 
       { 
        return propertyInfo.Name; 
       } 
       else 
       { 
        // we've got a nested property (e.g. MyType.SomeProperty.SomeNestedProperty) 
        return BuildString(memberExpression.Expression) + "." + propertyInfo.Name; 
       } 

      default: 
       // drop out and throw 
       break; 
     } 
     throw new InvalidOperationException("Expression must be a member expression: " + propertySelector.ToString()); 
    } 

Entonces, hacer caso omiso de un atributo, que puede muy bien añadirlo a la lista de ignorados:

var overrides = new XmlAttributeOverrides(); 
    var ignore = new XmlAttributes { XmlIgnore = true }; 
    overrides.Add<MyClass>(m => m.Id, ignore); 
    overrides.Add<MyClass>(m => m.DateChanged, ignore); 
    Type t = typeof(List<MyClass>); 
    XmlSerializer serial = new XmlSerializer(t, overrides); 
+3

Cualquier 'XmlSerializer' construido con' XmlAttributeOverrides' solo debe construirse una vez y luego almacenarse en caché para su posterior reutilización. Para obtener una explicación, consulte [Fuga de memoria utilizando StreamReader y XmlSerializer] (https://stackoverflow.com/questions/23897145) y [XmlSerializer extraTypes memory leak] (https://stackoverflow.com/questions/38892352). – dbc

3

Si está usando XmlSerializer, XmlAttributeOverrides es probablemente lo que necesita.

Actualización: He estado investigando las posibilidades de personalizar el formato de fecha y, por lo que puedo ver, no existen soluciones bonitas.

Una opción, como se ha mencionado por otros, es implementar IXmlSerializable. Esto tiene el inconveniente de que usted es completamente responsable de (des) serializar todo el objeto (-graph).

Una segunda opción, con una lista bastante extensa de inconvenientes, es subclasificar la clase base (la mencionaste como una alternativa en tu publicación). Con bastante fontanería, conversiones desde y hacia el objeto original, y el uso de XmlAttributeOverrides se podría construir algo como esto:

public class Test 
{ 
    public int Prop { get; set; } 
    public DateTime TheDate { get; set; } 
} 

public class SubTest : Test 
{ 
    private string _customizedDate; 
    public string CustomizedDate 
    { 
     get { return TheDate.ToString("yyyyMMdd"); } 
     set 
     { 
      _customizedDate = value; 
      TheDate = DateTime.ParseExact(_customizedDate, "yyyyMMdd", null); 
     } 
    } 

    public Test Convert() 
    { 
     return new Test() { Prop = this.Prop }; 
    } 
} 

// Serialize 
XmlAttributeOverrides overrides = new XmlAttributeOverrides(); 
XmlAttributes attributes = new XmlAttributes(); 
attributes.XmlIgnore = true; 
overrides.Add(typeof(Test), "TheDate", attributes); 

XmlSerializer xs = new XmlSerializer(typeof(SubTest), overrides); 
SubTest t = new SubTest() { Prop = 10, TheDate = DateTime.Now, CustomizedDate="20120221" }; 
xs.Serialize(fs, t); 

// Deserialize 
XmlSerializer xs = new XmlSerializer(typeof(SubTest)); 
SubTest t = (SubTest)xs.Deserialize(fs); 
Test test = t.Convert(); 

No es bonita, pero va a trabajar.

Tenga en cuenta que en este caso está (des) serializando objetos SubTest. Si el tipo exacto es importante, tampoco va a ser una opción.

7

Puede excluir algunas propiedades al aprovechar el hecho de que XmlSerializer no serializará valores nulos en la salida. Por lo tanto, para los tipos de referencia, puede anular las propiedades que no desea que aparezcan en el xml.

El xml resultante será deserializable de nuevo en la misma clase, pero los campos omitidos obviamente serán nulos.

Sin embargo, esto no ayuda para su deseo de cambiar el formato de fecha. Para esto, necesitará crear una nueva clase que tenga la fecha como una cadena en el formato que desee, o puede implementar IXmlSerializable, que le da control total sobre el xml. [Vale la pena señalar que el tipo de datos de fecha tiene un formato estándar en XML, por lo que al cambiarlo ya no será estrictamente una fecha XML, puede que no te importe].

[EDITAR en respuesta a sus comentarios]

Hay un truco adicional que pueda utilizar para "desaparecer" un tipo anulable nula, pero sí requiere una modificación de su clase. El serializador, al serializar MyProperty - también verificará si hay una propiedad llamada MyProperySpecified. Si existe y devuelve false, la propiedad del elemento no es de serie:

public class Person 
{ 
    [XmlElement] 
    public string Name { get; set; } 

    [XmlElement] 
    public DateTime? BirthDate { get; set; } 

    public bool BirthDateSpecified 
    { 
     get { return BirthDate.HasValue; } 
    } 
} 

Si usted está preparado para añadir esta propiedad se puede hacer a eliminar los tipos anulables cuando nulo. De hecho, ahora que lo pienso, esta también puede ser una forma útil de eliminar otras propiedades, dependiendo de su escenario de uso.

+0

Cuando mis propiedades son nulas, reciben la etiqueta Schiavini

+1

¿Las ha marcado como 'IsNullable'? De ser así, esto fuerza valores nulos a ser serializados con' xsi : nil = "true" 'en lugar de omitirlos. –

+0

Algunos de ellos son 'DateTime?' O 'int?', Pero también cadenas. – Schiavini

2

La primera opción es usar la clase XmlAttributeOverrides.

O puede tratar de crear la clase derivada e implementar IXmlSerializable interface

public class Program 
{ 
    static void Main(string[] args) 
    { 
     StringWriter sr1 = new StringWriter(); 
     var baseSerializer = new XmlSerializer(typeof(Human)); 
     var human = new Human {Age = 30, Continent = Continent.America}; 
     baseSerializer.Serialize(sr1, human); 
     Console.WriteLine(sr1.ToString()); 
     Console.WriteLine(); 

     StringWriter sr2 = new StringWriter(); 
     var specialSerializer = new XmlSerializer(typeof(SpecialHuman)); 
     var special = new SpecialHuman() {Age = 40, Continent = Continent.Africa}; 
     specialSerializer.Serialize(sr2, special); 
     Console.WriteLine(sr2.ToString()); 
     Console.ReadLine(); 
    } 

    public enum Continent 
    { 
     Europe, 
     America, 
     Africa 
    } 

    public class Human 
    { 
     public int Age { get; set; } 
     public Continent Continent { get; set; } 
    } 

    [XmlRoot("Human")] 
    public class SpecialHuman : Human, IXmlSerializable 
    { 
     #region Implementation of IXmlSerializable 

     /// <summary> 
     /// This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class. 
     /// </summary> 
     /// <returns> 
     /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method. 
     /// </returns> 
     public XmlSchema GetSchema() 
     { 
      throw new NotImplementedException(); 
     } 

     public void ReadXml(XmlReader reader) 
     { 
      throw new NotImplementedException(); 
     } 

     public void WriteXml(XmlWriter writer) 
     { 
      writer.WriteElementString("Age", Age.ToString()); 
      switch(Continent) 
      { 
       case Continent.Europe: 
       case Continent.America: 
        writer.WriteElementString("Continent", this.Continent.ToString()); 
        break; 
       case Continent.Africa: 
        break; 
       default: 
        throw new ArgumentOutOfRangeException(); 
      } 
     } 

     #endregion 
    } 

} 
+0

Esto se ve bien pero con muchas propiedades aún requeriría una gran cantidad de codificación manual .. – Schiavini

Cuestiones relacionadas