How to exclude null properties when using XmlSerializer
Asked Answered
K

10

53

I'm serializing a class like this

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

All of the types are nullable because I want minimal data stored when serializing an object of this type. However, when it is serialized with only "a" populated, I get the following xml

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

How do I set this up to only get xml for the non null properties? The desired output would be

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

I want to exclude these null values because there will be several properties and this is getting stored in a database (yeah, thats not my call) so I want to keep the unused data minimal.

Kensell answered 7/10, 2009 at 18:24 Comment(3)
If you added up all the time developers waste trying to get xml to look how they think it should look... you'd have a whole crapton of developer hours. I gave up long ago. You should consider that as an option.Tita
@Will, I normally would, no problem at all, but this will be used thousands of times a day and the whole class, serialized, is about 1000 characters, thats if all the properties are null! Also, all this is going in the db, not my choice :(Kensell
This is a good question, but I think it's a duplicate of #1296968 (which Marc Gravell answered by discussing the specification pattern).Pasteurizer
B
6

I suppose you could create an XmlWriter that filters out all elements with an xsi:nil attribute, and passes all other calls to the underlying true writer.

Bergin answered 7/10, 2009 at 19:14 Comment(5)
I like it, very simple, great idea :)Kensell
I posted my implementation of this (and also the option of skipping namespaces) at plus.google.com/+JoelElliott27/posts/ePPuwnH5Za8Genaro
@Genaro thanks for your post, but it doesn't remove the complete element. Or am I missing something?Bromeosin
@ArieKanarie, no, it only removes nil attributes, which is all I wanted to do. To remove the element, you'd have to do something beyond injecting into the plain XmlWriter, because you normally write the element before you know about the attribute which tells you it is nil.Genaro
@Genaro your post went away. Sad.Metry
K
57

You ignore specific elements with 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; } }
}

The {field}Specified properties will tell the serializer if it should serialize the corresponding fields or not by returning true/false.

Kensell answered 7/10, 2009 at 18:25 Comment(9)
note: I answered this because honestly, I'm looking for a better solution, I'm not a big fan of all these extra fields as my class has SEVERAL fields to serializeKensell
I may have misunderstood your 'bonus', but null strings are automatically omitted, without the xsi:nil="true" flotsam.Pasteurizer
@Jeff, Ah, so they are :-P ThanksKensell
No problem - and incidentally, I hope someone more ingenious than me comes up with a more elegant workaround than the specification pattern. :)Pasteurizer
@JeffSternal I think it's the only way to remain flexible. The other solution is to take control of the writer and imperatively write the xml rather than declaratively so to speak.Acarology
Also, unfortunately setting IsNull to false on a nullable int for example will throw an exception :(Acarology
Good luck when your using classes generated from the xsd tool.Romulus
I knew I could always count on you @AllenRiceInger
@Romulus that's why the classes should be generated as partial classes; you can add your customisation to a separate partial class file, so if the main partial class file ever gets regenerated from e.g. an updated XSD, your customisations won't get overwritten.Serotherapy
B
6

I suppose you could create an XmlWriter that filters out all elements with an xsi:nil attribute, and passes all other calls to the underlying true writer.

Bergin answered 7/10, 2009 at 19:14 Comment(5)
I like it, very simple, great idea :)Kensell
I posted my implementation of this (and also the option of skipping namespaces) at plus.google.com/+JoelElliott27/posts/ePPuwnH5Za8Genaro
@Genaro thanks for your post, but it doesn't remove the complete element. Or am I missing something?Bromeosin
@ArieKanarie, no, it only removes nil attributes, which is all I wanted to do. To remove the element, you'd have to do something beyond injecting into the plain XmlWriter, because you normally write the element before you know about the attribute which tells you it is nil.Genaro
@Genaro your post went away. Sad.Metry
B
5

Yet Another Solution: regex to the rescue, use \s+<\w+ xsi:nil="true" \/> to remove all null properties from a string containing XML. I agree, not the most elegant solution, and only works if you only have to serialize. But that was all I needed today, and I don't wanted to add {Foo}Specified properties for all the properties that are nullable.

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;
}
Bromeosin answered 6/2, 2017 at 16:34 Comment(0)
S
5

Somebody asked this question quite a long time ago, and it still seems VERY relevant, even in 2017. None of the proposed answers here weren't satisfactory to me; so here's a simple solution I came up with:

Using regular expressions is the key. Since we haven't much control over the XmlSerializer's behavior, let's NOT try to prevent it from serializing those nullable value types. Instead, take the serialized output and replace the unwanted elements with an empty string using Regex. The pattern used (in C#) is:

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

Here's an example:

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

I hope this helps.

Smew answered 15/5, 2017 at 15:43 Comment(0)
B
3

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) Create XElement

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

3) Remove Nil's

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

4) Save to file

xml.Save(xmlFilePath);
Bink answered 9/2, 2017 at 13:6 Comment(1)
This is not for the XmlSerializer.Textile
E
2

If you make the class you want to serialise implement IXmlSerializable, you can use the following writer. Note, you will need to implement a reader, but thats not too hard.

    public void WriteXml(XmlWriter writer)
    {
        foreach (var p in GetType().GetProperties())
        {
            if (p.GetCustomAttributes(typeof(XmlIgnoreAttribute), false).Any())
                continue;

            var value = p.GetValue(this, null);

            if (value != null)
            {
                writer.WriteStartElement(p.Name);
                writer.WriteValue(value);
                writer.WriteEndElement();
            }
        }
    }
Euryale answered 25/3, 2019 at 12:39 Comment(0)
A
1

Better late than never...

I found a way (maybe only available with the latest framework I don't know) to do this. I was using DataMember attribute for a WCF webservice contract and I marked my object like this:

[DataMember(EmitDefaultValue = false)]
public decimal? RentPrice { get; set; }
Alcoholize answered 7/4, 2014 at 14:34 Comment(2)
DataMember lives in namespace System.Runtime.Serialization while the svc serializer uses System.Xml.Serialization. this wont work unless youře using other serializer.Tutto
Yeah, this would only work with the DataContractSerializer, not the XmlSerializerRomulus
S
0

The simplest way of writing code like this where the exact output is important is to:

  1. Write an XML Schema describing your exact desired format.
  2. Convert your schema to a class using xsd.exe.
  3. Convert your class back to a schema (using xsd.exe again) and check it against your original schema to make sure that the serializer correctly reproduced every behaviour you want.

Tweak and repeat until you have working code.

If you are not sure of the exact data types to use initially, start with step 3 instead of step 1, then tweak.

IIRC, for your example you will almost certainly end up with Specified properties as you have already described, but having them generated for you sure beats writing them by hand. :-)

Swinger answered 7/10, 2009 at 19:20 Comment(0)
P
0

If you can accept the overhead that this would bring, rather than serializing directly to string, write to a LINQ XDocument directly where you can post process the serialization. Using regular expressions like the other answers suggest will be very brittle.

I wrote this method to return the LINQ object, but you can always call ToString() on it.

public XElement XmlSerialize<T>(T obj)
{
    var doc = new XDocument();
    var serializer = new XmlSerializer(typeof(T));
    using (var writer = doc.CreateWriter())
        serializer.Serialize(writer, obj);
    doc.Descendants()
        .Where(x => (bool?)x.Attribute(XName.Get("nil", "http://www.w3.org/2001/XMLSchema-instance")) == true)
        .Remove();
    return doc.Root!;
}
Pomace answered 14/1, 2022 at 21:15 Comment(0)
K
-3

Mark the element with [XmlElement("elementName", IsNullable = false)] null values will be omitted.

Karsten answered 21/12, 2012 at 7:31 Comment(2)
This won't work. The XmlSerializer throws an exception when a property of type int? is cnfigured like this.Desinence
This worked for me when using XmlMediaTypeFormatterEustis

© 2022 - 2024 — McMap. All rights reserved.