57

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.

3
  • 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. Commented Oct 7, 2009 at 18:33
  • 2
    @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 :( Commented Oct 7, 2009 at 18:37
  • 4
    This is a good question, but I think it's a duplicate of stackoverflow.com/questions/1296468/… (which Marc Gravell answered by discussing the specification pattern). Commented Oct 7, 2009 at 18:51

11 Answers 11

60

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.

Sign up to request clarification or add additional context in comments.

9 Comments

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 serialize
I may have misunderstood your 'bonus', but null strings are automatically omitted, without the xsi:nil="true" flotsam.
@Jeff, Ah, so they are :-P Thanks
No problem - and incidentally, I hope someone more ingenious than me comes up with a more elegant workaround than the specification pattern. :)
@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.
|
8

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.

Comments

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.

4 Comments

I like it, very simple, great idea :)
@Abacus thanks for your post, but it doesn't remove the complete element. Or am I missing something?
@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.
@Abacus your post went away. Sad.
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;
}

1 Comment

This is fine for lightweight xml.
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);

1 Comment

This is not for the XmlSerializer.
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();
            }
        }
    }

Comments

1

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

Comments

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. :-)

Comments

0

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

2 Comments

DataMember lives in namespace System.Runtime.Serialization while the svc serializer uses System.Xml.Serialization. this wont work unless youře using other serializer.
Yeah, this would only work with the DataContractSerializer, not the XmlSerializer
-1

Alternative - use an option which XmlSerializer has. To exclude a property from serialization using your rule, just add ShouldSerialize method to your model

example:

[XmlElement(ElementName = "engine_power")]
public decimal? EnginePower { get; set; }

// XmlSerializer checks internally for a method 
// with the name ShouldSerialize<PropertyName>
public bool ShouldSerializeEnginePower()
{
    // propert will not be in XML if value is NULL
    return EnginePower.HasValue;
}

Comments

-3

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

2 Comments

This won't work. The XmlSerializer throws an exception when a property of type int? is cnfigured like this.
This worked for me when using XmlMediaTypeFormatter

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.