C# 使用 XmlSerializer 时如何排除空属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1533335/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 18:30:20  来源:igfitidea点击:

How to exclude null properties when using XmlSerializer

c#xml-serializationnullable

提问by Allen Rice

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

所有类型都是可为空的,因为我希望在序列化这种类型的对象时存储最少的数据。但是,当它仅填充“a”进行序列化时,我得到以下 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

如何将其设置为仅获取非空属性的 xml?所需的输出将是

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

我想排除这些空值,因为将有多个属性,并且这将存储在数据库中(是的,那不是我的调用),因此我希望将未使用的数据保持在最低限度。

采纳答案by Martin v. L?wis

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.

我想您可以创建一个 XmlWriter 来过滤掉具有 xsi:nil 属性的所有元素,并将所有其他调用传递给底层真正的编写器。

回答by Allen Rice

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.

{field}Specified 属性将通过返回 true/false 告诉序列化器是否应该序列化相应的字段。

回答by Christian Hayter

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.exeagain) and check it against your original schema to make sure that the serializer correctly reproduced every behaviour you want.
  1. 编写一个 XML 模式来描述您确切所需的格式。
  2. 使用 将您的架构转换为类xsd.exe
  3. 将您的类转换回模式(xsd.exe再次使用)并根据您的原始模式检查它,以确保序列化程序正确地重现了您想要的每个行为。

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.

如果您不确定最初要使用的确切数据类型,请从第 3 步而不是第 1 步开始,然后进行调整。

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

IIRC,对于你的例子,你几乎肯定会得到Specified你已经描述过的属性,但是为你生成它们肯定比手工编写它们好。:-)

回答by user1920925

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

用 [XmlElement("elementName", IsNullable = false)] 标记元素将省略空值。

回答by Aurel

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 属性用于 WCF Web 服务合同,并像这样标记了我的对象:

[DataMember(EmitDefaultValue = false)]
public decimal? RentPrice { get; set; }

回答by ArieKanarie

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}Specifiedproperties for all the properties that are nullable.

另一个解决方案:正则表达式来拯救,用于\s+<\w+ xsi:nil="true" \/>从包含 XML 的字符串中删除所有空属性。我同意,这不是最优雅的解决方案,只有在您只需要序列化时才有效。但这就是我今天所需要的,我不想{Foo}Specified为所有可以为空的属性添加属性。

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

回答by Krzysztof Radzimski

1) Extension

1) 扩展

 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

1a) Utf8StringWriter

public class Utf8StringWriter : StringWriter {
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

2) Create XElement

2) 创建 XElement

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

3) Remove Nil's

3) 删除 Nil

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

4) 保存到文件

xml.Save(xmlFilePath);

回答by Bigabdoul

This question's been asked quite a long time ago but still is 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:

这个问题很久以前就有人问过了,但即使在 2017 年仍然非常相关。这里提出的答案都没有让我满意,所以这是我想出的一个简单的解决方案:

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

使用正则表达式是关键。由于我们对 XmlSerializer 的行为没有太多控制,所以我们不要试图阻止它序列化那些可为 null 的值类型。相反,只需获取序列化的输出并使用 Regex 用空字符串替换不需要的元素。使用的模式(在 C# 中)是:

<\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.

我希望这有帮助。

回答by BJury

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.

如果您使要序列化的类实现 IXmlSerializable,则可以使用以下编写器。请注意,您需要实现一个阅读器,但这并不难。

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