0

I want to reduce the clutter in serialization output, introducing default values for often-the-same or not-used properties.

But still, they are in the output. What am I doing wrong?

This should be complete (though not compile):

[Serializable]
public class MyClass
{
    [DefaultValue(null)]
    public string Alias { get; set; } = null;

    [DefaultValue(false)]
    public bool Deactivated { get; set; } = false;

    [DefaultValue(null)]
    public bool? MyNullable { get; set; } = null;
}

public static string SerializeFromObject<T>(this T toSerialize)
{
    var xmlSerializer = new XmlSerializer(toSerialize.GetType());
    using (StringWriter textWriter = new StringWriter())
    {
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}

var myClass = new MyClass();
var str = SerializeFromObject(myClass);

And here the xml output, still including the nullable:

<?xml version="1.0" encoding="utf-16"?>
<MyClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <MyNullable xsi:nil="true" />
</MyClass>

How to get rid of the nullable in the serialized xml?

Andreas Reiff
  • 7,961
  • 10
  • 50
  • 104
  • nil comes from XmlElementAttribute.IsNullable Property https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlelementattribute.isnullable?redirectedfrom=MSDN&view=net-5.0#System_Xml_Serialization_XmlElementAttribute_IsNullable – Self Mar 18 '21 at 08:29
  • 2
    So DefaultValueAttribute should not change the XmlElementAttribute.IsNullable behavior. But DataContract as a DataMemberAttribute.EmitDefaultValue Property, that you will have better luck with. But you may not like the DataContractSerializer. – Self Mar 18 '21 at 08:53
  • Thank you! Is there a copy/paste solution somewhere? Also, is that backwards compatible (able to read previously serialized classes)? – Andreas Reiff Mar 18 '21 at 08:59
  • 1
    Here is a short copy past in a fiddle. https://dotnetfiddle.net/lzB9QM. you will have to had the System.Runtime reference.. DataContract doesnt has encoding headear like Xml has so you may have mismatch. For compatibility if the only Xml property you use are DefaultValue, you will be ok. But for more complex class I can't tell. – Self Mar 18 '21 at 09:17
  • That is very helpful, many thanks! It seems though like the DefaultValue attribute has no effect. It seems to only support the DefaultValue of the type. So a boolean default will always be false and cannot be set to true for a specific property. The default xml serializer did support that. https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datamemberattribute.emitdefaultvalue?view=net-5.0 – Andreas Reiff Mar 18 '21 at 11:59
  • @Self Can you/do you want to make a proper answer out of this? I would certainly upvote. Apart from that, I would probably go with my own solution. I just tested that I can serialize, convert back to XmlElement, parse through nodes, remove null nodes and output as string. No support for DefaultValue on the nullables - just null is the default, also not a contender for a beauty contest. Always surprised how many hickups there are with xml serialization (Dictionary, anyone?). – Andreas Reiff Mar 18 '21 at 15:12
  • 1
    Doesn't feel like an answers to me. While the question is clear , and I know why it behave that way. I also knew that DataContract was not the way to go. If you find a way to write an undestandable answer you can take everything. Btw just had an Idea. perhaps you can just serilize to Json using the good old Json.net and then Json To Xml using the same tool. That may solve your issue. – Self Mar 18 '21 at 15:24
  • 2
    Take a look at [Xml serialization - Hide null values](https://stackoverflow.com/q/5818513/3744182). For nullable value types, it seems there's no attribute that can suppress their serialization, and so one of the [conditional serialization patterns](https://stackoverflow.com/q/37838640/3744182) is required. – dbc Mar 18 '21 at 16:07
  • @Self I included your answer, perhaps someone can benefit. If that's not ok, I will remove it. Thanks for all the time taken and insight! Thanks to dbc, too! – Andreas Reiff Mar 18 '21 at 17:54
  • See this is why it doesn't feel like an answer. I knew Darin, Thomas, or dbc will eventually show up and shrug it off. (Xml serialization - Hide null values)[https://stackoverflow.com/questions/5818513/]. feels like a good dupe target now. – Self Mar 19 '21 at 07:50

1 Answers1

0

Self's answer was pretty helpful, but in the end there were some issues so I didn't follow it. I will outline it here later on for posterity though so it doesn't get lost in the comments or through a link going offline.

My own solution:

use standard .net xml serialization, re-read serialized string into XElement, remove all "nil"s. And then .ToString() it again.

Definitely a medium-nice solution, so feel free to come up with something nicer.

Conditional serialization as suggested would mean too much additional code for me which I would like to avoid.

My solution also has the disadvantage that DefaultValues cannot be specified for the nullables, they are always omitted when being null. Which is fine for me though. I use a nullable when I have no default value.

    /// <summary>
    /// use for compact serializations
    /// nullables that don't have a value are omitted (irrespecitve of DefaultValue!)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="toSerialize"></param>
    /// <returns></returns>
    public static string SerializeFromObject_NoNils<T>(T toSerialize)
    {
        var ele = Serialize<T>(toSerialize);
        void removeNils(XNode node)
        {
            // recursion
            if (node is XElement elem)
            {
                foreach (var child in elem.DescendantNodes())
                    removeNils(child);
                //foreach (var child in elem.Descendants())
                //    removeNils(child);
            }

            // same level
            while (node != null)
            {
                var nextnode = node.NextNode;
                //if (node.)
                if ((node as System.Xml.Linq.XElement)?.Attribute("{http://www.w3.org/2001/XMLSchema-instance}nil")?.Value == "true")
                    node.Remove();
                node = nextnode;
            }
        }

        removeNils(ele.FirstNode);
        return ele.ToString();
    }

If someone want to build on or improve Self's answer - which has the disadvantage, that the DefaultValue attribute seems not to work (it seems to work on default(type) rather than that attribute), here it is copy/pasted from his link, with an added empty namespace because the default .net deserialization stumbles across the DataSerializerContract namespace.

So, this is not my code, credit goes to user Self.

using System;
using System.Collections.Generic;
using System.Linq;

using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.ComponentModel;

using System.Runtime.Serialization;

public class Program
{
    public static void Main()
    {

        var myClass = new MyClass();
        var str_dc = DataContract_SerializeFromObject(myClass);
        str_dc.Dump();
        
        
        var str_xml = SerializeFromObject(myClass);
        str_xml.Dump();
    }


    public static string SerializeFromObject<T>( T toSerialize)
    {
        var xmlSerializer = new XmlSerializer(toSerialize.GetType());
        using (StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static string DataContract_SerializeFromObject<T>( T toSerialize)
    {
        var xmlSerializer = new DataContractSerializer(toSerialize.GetType());
        
        using (var output = new StringWriter())
        using (var writer = new XmlTextWriter(output) { Formatting = Formatting.Indented })
        {
            xmlSerializer.WriteObject(writer, toSerialize);
            return output.GetStringBuilder().ToString();
        }
    }
}

[DataContract(Namespace = "")] // default namespace is not deserializable with standard functionality   
[Serializable]
public class MyClass
{
    [DataMember(EmitDefaultValue = false)] [DefaultValue(null)]
    public string Alias { get; set; } = null;

    [DataMember(EmitDefaultValue = false)] [DefaultValue(false)]
    public bool Deactivated { get; set; } = false;

    [DataMember(EmitDefaultValue = false)] [DefaultValue(null)]
    public bool? MyNullable { get; set; } = null;
}
Andreas Reiff
  • 7,961
  • 10
  • 50
  • 104