web-dev-qa-db-ja.com

XmlSerializerを使用するときにnullプロパティを除外する方法

このようなクラスをシリアル化しています

public MyClass
{
    public int? a { get; set; }
    public int? b { get; set; }
    public int? c { get; set; }
}

このタイプのオブジェクトをシリアル化するときに最小限のデータを格納する必要があるため、すべてのタイプがnull可能です。ただし、「a」のみを入力してシリアル化すると、次のxmlが表示されます

<MyClass ...>
    <a>3</a>
    <b xsi:nil="true" />
    <c xsi:nil="true" />
</MyClass>

Null以外のプロパティのxmlのみを取得するように設定するにはどうすればよいですか?望ましい出力は

<MyClass ...>
    <a>3</a>
</MyClass>

いくつかのプロパティがあり、これはデータベースに保存されている(そうではありません)ため、これらのnull値を除外したいので、未使用のデータを最小限に抑えます。

31
Allen Rice

Xsi:nil属性を持つすべての要素をフィルターで除外し、他のすべての呼び出しを基になる真のライターに渡すXmlWriterを作成できると思います。

6

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

{field} Specifiedプロパティは、true/falseを返すことにより、対応するフィールドをシリアル化する必要があるかどうかをシリアライザに伝えます。

33
Allen Rice

決して遅くないよりはまし...

私はこれを行う方法を見つけました(多分私が知らない最新のフレームワークでのみ利用可能です)。 WCF WebサービスコントラクトにDataMember属性を使用していて、次のようにオブジェクトをマークしました。

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

さらに別の解決策:救済策としての正規表現、\s+<\w+ xsi:nil="true" \/>を使用して、XMLを含む文字列からすべてのnullプロパティを削除します。私は同意します。最もエレガントなソリューションではなく、シリアル化する必要がある場合にのみ機能します。しかし、今日必要なのはそれだけで、null許容のすべてのプロパティに{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;
}
3
ArieKanarie

この質問はかなり前に尋ねられましたが、2017年でもまだ非常に関連があります。ここで提案された回答のどれも私にとって満足のいくものではなかったので、私が思いついた簡単な解決策は次のとおりです。

正規表現を使用することが重要です。 XmlSerializerの動作をあまり制御できないので、これらのnull許容値型のシリアル化を妨げないようにしましょう。代わりに、シリアル化された出力を取得し、Regexを使用して不要な要素を空の文字列に置き換えます。 (C#で)使用されるパターンは次のとおりです。

<\w+\s+\w+:nil="true"(\s+xmlns:\w+="http://www.w3.org/2001/XMLSchema-instance")?\s*/>

次に例を示します。

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

これがお役に立てば幸いです。

2
Bigabdoul

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

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

2)XElementを作成する

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

3)ニルの削除

xml.Descendants().Where(x => x.Value.IsNullOrEmpty() && x.Attributes().Where(y => y.Name.LocalName == "nil" && y.Value == "true").Count() > 0).Remove();

4)ファイルに保存

xml.Save(xmlFilePath);
2

シリアル化するクラスに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();
            }
        }
    }
0
BJury

正確な出力が重要なこのようなコードを記述する最も簡単な方法は、次のとおりです。

  1. 希望する正確な形式を記述するXMLスキーマを記述します。
  2. xsd.exeを使用してスキーマをクラスに変換します。
  3. クラスをスキーマに変換し直し(xsd.exeを再度使用)、元のスキーマと照合して、シリアライザが必要なすべての動作を正しく再現していることを確認します。

作業コードができるまで微調整して繰り返します。

最初に使用する正確なデータ型がわからない場合は、ステップ1ではなくステップ3から始め、次に微調整します。

IIRC、あなたの例では、あなたはすでに説明したように、ほぼ間違いなくSpecifiedプロパティで終わるでしょうが、それらを生成することは、それらを手で書くことより確かです。 :-)

0