C# XmlSerializer DefaultAttribute 属性仍然为 nullables 序列化

Posted

技术标签:

【中文标题】C# XmlSerializer DefaultAttribute 属性仍然为 nullables 序列化【英文标题】:C# XmlSerializer DefaultAttribute property still serialized for nullables 【发布时间】:2021-06-15 15:01:00 【问题描述】:

我想减少序列化输出中的混乱,为经常相同或不使用的属性引入默认值。

但是,它们仍然在输出中。我做错了什么?

这应该是完整的(虽然没有编译):

[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);

这里是 xml 输出,仍然包括可为空的:

<?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>

如何去掉序列化 xml 中的 nullable?

【问题讨论】:

nil 来自 XmlElementAttribute.IsNullable 属性docs.microsoft.com/en-us/dotnet/api/… 因此 DefaultValueAttribute 不应更改 XmlElementAttribute.IsNullable 行为。但是 DataContract 作为一个 DataMemberAttribute.EmitDefaultValue 属性,你会有更好的运气。但你可能不喜欢 DataContractSerializer。 这是一个简短的副本过去。 dotnetfiddle.net/lzB9QM。你必须有 System.Runtime 参考。DataContract 没有像 Xml 那样的编码头,所以你可能有不匹配。如果您使用的唯一 Xml 属性是 DefaultValue,则为了兼容性,您会没事的。但是对于更复杂的类我说不出来。 感觉不像是我的答案。虽然问题很清楚,但我知道它为什么会这样。我也知道 DataContract 不是要走的路。如果你找到一种写出难以理解的答案的方法,你可以接受一切。顺便说一句,刚刚有一个想法。也许您可以使用旧的 Json.net 将 Json 序列化为 Json,然后使用相同的工具将 Json To Xml 序列化。这可能会解决您的问题。 看看Xml serialization - Hide null values。对于可空值类型,似乎没有可以抑制其序列化的属性,因此需要conditional serialization patterns 之一。 【参考方案1】:

Self 的回答非常有帮助,但最后出现了一些问题,所以我没有遵循它。稍后我将在此处概述它以供后人使用,以免它在 cmets 中或通过离线链接丢失。

我自己的解决方案:

使用标准 .net xml 序列化,将序列化字符串重新读入 XElement,删除所有“nil”。然后 .ToString() 再次。

绝对是一个中等好的解决方案,所以请随时提出更好的解决方案。

建议的条件序列化对我来说意味着太多我想避免的额外代码。

我的解决方案还有一个缺点,即不能为 nullables 指定 DefaultValues,它们在为 null 时总是被省略。不过这对我来说很好。当我没有默认值时,我使用 nullable。

    /// <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-instancenil")?.Value == "true")
                    node.Remove();
                node = nextnode;
            
        

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

如果有人想建立或改进 Self 的答案 - 它有一个缺点,即 DefaultValue 属性似乎不起作用(它似乎适用于 default(type) 而不是该属性),这里复制/粘贴自他的链接,添加了一个空的命名空间,因为默认的 .net 反序列化会偶然发现 DataSerializerContract 命名空间。

所以,这不是我的代码,归功于用户 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;

【讨论】:

以上是关于C# XmlSerializer DefaultAttribute 属性仍然为 nullables 序列化的主要内容,如果未能解决你的问题,请参考以下文章

C# Xmlserializer 将列表反序列化为 0 而不是 null

C#中通过XmlSerializer类反序列化多个同名XML元素

C# 在 XmlSerializer 中使用结构而不创建单独的 xml 节点

C# XMLSerializer 将错误的类型反序列化为 List

C# XmlSerializer 用不同的命名空间序列化同一个类

C#语言中的XmlSerializer类的Serialize(Stream,Object)方法举例详解