2012-02-21 8 views
12

Sto provando a serializzare un oggetto con diverse proprietà, ma non voglio includere tutte le proprietà nella serializzazione. Inoltre, vorrei cambiare il formato della data.Escludendo alcune proprietà durante la serializzazione senza modificare la classe originale

Ovviamente potrei aggiungere [XmlIgnore], ma non sono autorizzato a modificare la classe originale.

L'unica opzione che potevo pensare era creare una nuova classe e copiare tutti i contenuti tra le due classi. Ma sarebbe brutto e richiederebbe molto codice manuale.

Potrebbe forse essere possibile creare una sottoclasse, poiché l'originale non è astratto?

La mia domanda è dunque:

  1. Come posso escludere alcune proprietà senza modificare la classe originale?

  2. Come è possibile personalizzare il formato della data dell'XML di output?

Requisiti:

  1. quanto forte tipizzato possibile

  2. XML serializzato dovrebbero essere deserializable

Grazie in anticipo.

+0

A proposito, stiamo usando .NET 4.0 – Schiavini

+0

E il XmlSerializer – Schiavini

risposta

11

Per chi è interessato, ho deciso di usare XmlAttributeOverrides, ma li ha resi più forte tipizzato (io odio a digitare nomi di proprietà come stringhe). Ecco il metodo di estensione che ho usato per questo:

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

Poi, di ignorare un attributo, posso ben aggiungerlo alla lista ignora:

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

Qualsiasi 'XmlSerializer' costruito usando' XmlAttributeOverrides' dovrebbe essere costruito una sola volta, quindi memorizzato nella cache per riutilizzarlo in seguito. Per una spiegazione, vedere [Perdita di memoria con StreamReader e XmlSerializer] (https://stackoverflow.com/questions/23897145) e [XmlSerializer extraTypes perdita di memoria] (https://stackoverflow.com/questions/38892352). – dbc

3

Se si utilizza XmlSerializer, XmlAttributeOverrides è probabilmente quello che ti serve.

Aggiornamento: Ho studiato le possibilità di personalizzare il formato della data e, per quanto posso vedere, non esistono soluzioni carine.

Un'opzione, come è stato menzionato da altri, è l'implementazione di IXmlSerializable. Questo ha l'inconveniente di essere completamente responsabile della (de-) serializzazione dell'intero oggetto (-grafico).

Una seconda opzione, con una lista abbastanza ampia di inconvenienti, è quella di sottoclasse della classe base (l'hai citata come alternativa nel tuo post). Con un po 'di impianto idraulico, le conversioni da e per l'oggetto originale, e l'uso di XmlAttributeOverrides si potrebbe costruire qualcosa di simile:

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

Non è abbastanza, ma funzionerà.

Si noti che in questo caso si sta effettivamente (de-) serializzando oggetti SubTest. Se il tipo esatto è importante, anche questa non sarà un'opzione.

7

Potrebbe essere possibile escludere alcune proprietà sfruttando il fatto che lo XmlSerializer non serializzerà i null sull'output. Pertanto, per i tipi di riferimento, è possibile escludere quelle proprietà che non si desidera visualizzare nell'xml.

L'xml risultante sarà deserializzabile nella stessa classe, ma i campi omessi saranno ovviamente nulli.

Tuttavia, questo non è d'aiuto per il desiderio di modificare il formato della data. Per fare ciò, dovrai creare una nuova classe che abbia la data come stringa nel formato che desideri o che tu possa implementare IXmlSerializable, dandoti il ​​controllo completo su xml. [vale la pena notare che data-type data ha un formato standard in XML, quindi cambiandolo non sarà più una data XML più a lungo - potrebbe non interessarti].

[EDIT in risposta ai vostri commenti]

C'è un trucco aggiuntivo si potrebbe utilizzare per "sparire" un tipo nullable nulla, ma richiede un cambiamento alla classe. Il serializzatore, durante la serializzazione MyProperty - verificherà anche se esiste una proprietà denominata MyProperySpecified. Se esiste e restituisce false, la proprietà oggetto non è serializzato:

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

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

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

Se siete disposti ad aggiungere questa proprietà si può fare rimuovere i tipi nullable quando null. In effetti, ora ci penso, questo può essere un modo utile per rimuovere anche altre proprietà, a seconda dello scenario di utilizzo.

+0

Quando le mie proprietà sono nulle, ottengono il tag Schiavini

+1

Le hai marcate come 'IsNullable' - in tal caso, forza i valori nulli da serializzare con' xsi : nil = "true" 'invece di ometterli. –

+0

Alcuni di questi sono 'DateTime?' O 'int?', Ma anche stringhe .. – Schiavini

2

Le prime opzioni sono l'uso della classe XmlAttributeOverrides.

Oppure si può cercare di creare classe derivata e implementare 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

Questo sembra buono ma con molte proprietà richiederebbe ancora un sacco di codice manuale. – Schiavini

Problemi correlati