c# - xmlelement - Cómo excluir propiedades nulas al usar XmlSerializer
xmlelementattribute (9)
¿Has leído SO: Serialize a nullable int ?
Estoy serializando una clase como esta
public MyClass
{
public int? a { get; set; }
public int? b { get; set; }
public int? c { get; set; }
}
Todos los tipos son anulables porque quiero que se almacenen datos mínimos al serializar un objeto de este tipo. Sin embargo, cuando se serializa con solo "a", se obtiene el siguiente xml
<MyClass ...>
<a>3</a>
<b xsi:nil="true" />
<c xsi:nil="true" />
</MyClass>
¿Cómo configuro esto para obtener solo xml para las propiedades no nulas? La salida deseada sería
<MyClass ...>
<a>3</a>
</MyClass>
Quiero excluir estos valores nulos porque habrá varias propiedades y esto se está almacenando en una base de datos (sí, esa no es mi llamada), así que quiero mantener los datos no utilizados al mínimo.
1) Extensión
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 Nil''s
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);
Esta pregunta se hizo hace mucho tiempo, pero aún es MUY relevante incluso en 2017. Ninguna de las respuestas propuestas aquí no fue satisfactoria para mí, así que aquí hay una solución simple que se me ocurrió:
Usar las expresiones regulares es la clave. Ya que no tenemos mucho control sobre el comportamiento del XmlSerializer, así que NO intentemos evitar que serialice esos tipos de valores anulables. En su lugar, simplemente 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*/>
Aquí hay 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.
Ignoras elementos specification 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; } }
}
Las propiedades especificadas de {field} indicarán al serializador si debe serializar los campos correspondientes o no devolviendo verdadero / falso.
La forma más sencilla de escribir código como este donde el resultado exacto es importante es:
- Escriba un esquema XML que describa su formato exacto deseado.
- Convierte tu esquema a una clase usando
xsd.exe
. - Convierta su clase de nuevo a un esquema (usando
xsd.exe
nuevo) yxsd.exe
con su esquema original para asegurarse de que el serializador reproduzca correctamente todos los comportamientos que desee.
Modifica y repite hasta que tengas código de trabajo.
Si no está seguro de los tipos de datos exactos a usar inicialmente, comience con el paso 3 en lugar del paso 1, luego modifíquelo.
IIRC, para su ejemplo, es casi seguro que terminará con las propiedades Specified
como ya lo ha descrito, pero al generarlas para usted seguro que las escribe a mano. :-)
Marque el elemento con [XmlElement ("elementName", IsNullable = false)] se omitirán los valores nulos.
Mejor tarde que nunca...
Encontré una forma (tal vez solo disponible con el último marco que no conozco) para hacer esto. Estaba usando el atributo DataMember para un contrato de servicio web WCF y marqué mi objeto de la siguiente manera:
[DataMember(EmitDefaultValue = false)]
public decimal? RentPrice { get; set; }
Otra solución más: regex al rescate, use /s+</w+ xsi:nil="true" //>
para eliminar todas las propiedades nulas de una cadena que contiene XML. Estoy de acuerdo, 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 {Foo}Specified
para todas las propiedades que son anulables.
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;
}
Supongo que podría crear un XmlWriter que filtre todos los elementos con un atributo xsi: nil y pase todas las demás llamadas al verdadero escritor subyacente.