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