2009-10-07 12 views
27

estoy serializar una clase como estaCómo excluir propiedades nulos cuando se usa XmlSerializer

public MyClass 
{ 
    public int? a { get; set; } 
    public int? b { get; set; } 
    public int? c { get; set; } 
} 

Todos los tipos son anulable porque quiero un mínimo de datos almacenados al serializar un objeto de este tipo. Sin embargo, cuando se serializa sólo con "a" poblada, me sale el siguiente XML

<MyClass ...> 
    <a>3</a> 
    <b xsi:nil="true" /> 
    <c xsi:nil="true" /> 
</MyClass> 

¿Cómo fijo esto para conseguir solamente XML para las propiedades no nulos? La salida deseada sería

<MyClass ...> 
    <a>3</a> 
</MyClass> 

quiero excluir estos valores nulos, ya que habrá varias propiedades y esto se está poniendo almacena en una base de datos (sí, eso no es mi llamada) así que desee mantener los datos no utilizados mínimo .

+3

Si suma todo el tiempo que los desarrolladores desperdician tratando de obtener xml para ver cómo * piensan * debería verse ... tendrían una gran cantidad de horas de desarrollador. Me di por vencido hace mucho tiempo. Deberías considerar eso como una opción. – Will

+1

@ Will, normalmente lo haría, no hay problema en absoluto, pero esto se usará miles de veces al día y toda la clase, serializada, tiene alrededor de 1000 caracteres, ¡eso es si todas las propiedades son nulas! Además, todo esto va en el DB, no es mi elección :( –

+3

Esta es una buena pregunta, pero creo que es un duplicado de http://stackoverflow.com/questions/1296468/suppress-null-value-types-from -being-emitted-by-xmlserializer (que Marc Gravell respondió discutiendo el patrón de especificación). –

Respuesta

6

Supongo que puede crear un XmlWriter que filtra todos los elementos con un atributo xsi: nil, y pasa todas las demás llamadas al escritor verdadero subyacente.

+0

Me gusta, muy simple, buena idea :) –

+1

Publicó mi implementación de esto (y también la opción de omitir espacios de nombres) en https://plus.google.com/+JoelElliott27/posts/ePPuwnH5Za8 – Abacus

+0

@Abacus gracias por su publicación, pero no elimina el elemento completo. ¿O me estoy perdiendo algo? – ArieKanarie

28

te ignoran elementos específicos con specification

public MyClass 
{ 
    public int? a { get; set; } 

    [System.Xml.Serialization.XmlIgnore] 
    public bool aSpecified { get { return this.a != null; } } 

    public int? b { get; set; } 
    [System.Xml.Serialization.XmlIgnore] 
    public bool bSpecified { get { return this.b != null; } } 

    public int? c { get; set; } 
    [System.Xml.Serialization.XmlIgnore] 
    public bool cSpecified { get { return this.c != null; } } 
} 

El campo {} Las propiedades especificadas le dirá al serializador si se debe serializar los campos correspondientes o no mediante la devolución de verdadero/falso.

+2

nota: respondí esto porque, sinceramente, estoy buscando una mejor solución, no soy un gran fan de todos estos campos adicionales ya que mi clase tiene VARIOS campos para serializar –

+2

Pude haber entendido mal su "bonificación", pero las cadenas nulas se omiten automáticamente, sin el xsi: nil = "verdadero" despojo. –

+0

@Jeff, Ah, entonces son :-P Gracias –

0

La forma más sencilla de escribir código como este donde la salida exacta es importante es:

  1. Escribir un esquema XML que describe el formato deseado exacta.
  2. Convierta su esquema a una clase usando xsd.exe.
  3. Convierta su clase nuevamente en un esquema (utilizando xsd.exe nuevamente) y compruébelo con su esquema original para asegurarse de que el serializador reproduzca correctamente cada comportamiento que desee.

Modifique y repita hasta que tenga el código de trabajo.

Si no está seguro de los tipos de datos exactos que debe usar inicialmente, comience con el paso 3 en lugar del paso 1, luego realice ajustes.

IIRC, para su ejemplo, casi seguramente terminará con las propiedades Specified como ya ha descrito, pero tenerlas generadas para usted es mejor que escribirlas a mano. :-)

-2

Marque el elemento con [XmlElement ("elementName", IsNullable = false)] se omitirán los valores nulos.

+1

Esto no funcionará. El 'XmlSerializer' arroja una excepción cuando una propiedad de tipo' int? 'Se calcula así. –

3

vale tarde que nunca ...

he encontrado una manera (tal vez sólo está disponible con el último marco no sé) para hacer esto. que estaba usando DataMember atributo para un contrato de servicio web WCF y me marcó mi objeto como éste:

[DataMember(EmitDefaultValue = false)] 
public decimal? RentPrice { get; set; } 
+0

DataMember vive en el espacio de nombres System.Runtime.Serialization mientras que el serializador svc usa System.Xml.Serialization. esto no funcionará a menos que uses otro serializador. –

+0

Sí, esto solo funcionaría con el DataContractSerializer, no con el XmlSerializer – crush

2

otra solución: expresión regular para el rescate, utilice \s+<\w+ xsi:nil="true" \/> para eliminar todas las propiedades nulos de una cadena que contiene XML. Acepto, no es la solución más elegante, y solo funciona si solo tiene que serializar. Pero eso era todo lo que necesitaba hoy, y no quería agregar {Foo}Specified propiedades para todas las propiedades que pueden contener nulos.

public string ToXml() 
{ 
    string result; 

    var serializer = new XmlSerializer(this.GetType()); 

    using (var writer = new StringWriter()) 
    { 
     serializer.Serialize(writer, this); 
     result = writer.ToString(); 
    } 

    serializer = null; 

    // Replace all nullable fields, other solution would be to use add PropSpecified property for all properties that are not strings 
    result = Regex.Replace(result, "\\s+<\\w+ xsi:nil=\"true\" \\/>", string.Empty); 

    return result; 
} 
2

1) Extension

public static string Serialize<T>(this T value) { 
     if (value == null) { 
      return string.Empty; 
     } 
     try { 
      var xmlserializer = new XmlSerializer(typeof(T)); 
      var stringWriter = new Utf8StringWriter(); 
      using (var writer = XmlWriter.Create(stringWriter)) { 
       xmlserializer.Serialize(writer, value); 
       return stringWriter.ToString(); 
      } 
     } catch (Exception ex) { 
      throw new Exception("An error occurred", ex); 
     } 
    } 

1a) Utf8StringWriter

public class Utf8StringWriter : StringWriter { 
    public override Encoding Encoding { get { return Encoding.UTF8; } } 
} 

2) Crear XElement

XElement xml = XElement.Parse(objectToSerialization.Serialize()); 

3) Eliminar de Nil

xml.Descendants().Where(x => x.Value.IsNullOrEmpty() && x.Attributes().Where(y => y.Name.LocalName == "nil" && y.Value == "true").Count() > 0).Remove(); 

4) Guardar en archivo

xml.Save(xmlFilePath); 
2

Esta pregunta se ha pedido hace mucho tiempo, pero todavía es muy relevante incluso en 2017. Ninguna de las respuestas que aquí se propone no fuera satisfactorio para mí así que aquí tiene una solución simple que se me ocurrió:

El uso de expresiones regulares es la clave. Como no tenemos mucho control sobre el comportamiento del XmlSerializer, NO tratemos de impedir que serialice esos tipos de valor que aceptan valores. En su lugar, solo tome la salida serializada y reemplace los elementos no deseados con una cadena vacía usando Regex. El patrón utilizado (en C#) es:

<\w+\s+\w+:nil="true"(\s+xmlns:\w+="http://www.w3.org/2001/XMLSchema-instance")?\s*/> 

He aquí un ejemplo:

using System.IO; 
using System.Text; 
using System.Text.RegularExpressions; 
using System.Xml; 
using System.Xml.Serialization; 

namespace MyNamespace 
{ 
    /// <summary> 
    /// Provides extension methods for XML-related operations. 
    /// </summary> 
    public static class XmlSerializerExtension 
    { 
     /// <summary> 
     /// Serializes the specified object and returns the XML document as a string. 
     /// </summary> 
     /// <param name="obj">The object to serialize.</param> 
     /// <param name="namespaces">The <see cref="XmlSerializerNamespaces"/> referenced by the object.</param> 
     /// <returns>An XML string that represents the serialized object.</returns> 
     public static string Serialize(this object obj, XmlSerializerNamespaces namespaces = null) 
     { 
      var xser = new XmlSerializer(obj.GetType()); 
      var sb = new StringBuilder(); 

      using (var sw = new StringWriter(sb)) 
      { 
       using (var xtw = new XmlTextWriter(sw)) 
       { 
        if (namespaces == null) 
         xser.Serialize(xtw, obj); 
        else 
         xser.Serialize(xtw, obj, namespaces); 
       } 
      } 

      return sb.ToString().StripNullableEmptyXmlElements(); 
     } 

     /// <summary> 
     /// Removes all empty XML elements that are marked with the nil="true" attribute. 
     /// </summary> 
     /// <param name="input">The input for which to replace the content. </param> 
     /// <param name="compactOutput">true to make the output more compact, if indentation was used; otherwise, false.</param> 
     /// <returns>A cleansed string.</returns> 
     public static string StripNullableEmptyXmlElements(this string input, bool compactOutput = false) 
     { 
      const RegexOptions OPTIONS = 
      RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline; 

      var result = Regex.Replace(
       input, 
       @"<\w+\s+\w+:nil=""true""(\s+xmlns:\w+=""http://www.w3.org/2001/XMLSchema-instance"")?\s*/>", 
       string.Empty, 
       OPTIONS 
      ); 

      if (compactOutput) 
      { 
       var sb = new StringBuilder(); 

       using (var sr = new StringReader(result)) 
       { 
        string ln; 

        while ((ln = sr.ReadLine()) != null) 
        { 
         if (!string.IsNullOrWhiteSpace(ln)) 
         { 
          sb.AppendLine(ln); 
         } 
        } 
       } 

       result = sb.ToString(); 
      } 

      return result; 
     } 
    } 
} 

espero que esto ayude.

Cuestiones relacionadas