Подтвердить что ты не робот

Как исключить нулевые свойства при использовании XmlSerializer

Я сериализую класс, подобный этому

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

Все типы являются нулевыми, потому что я хочу, чтобы минимальные данные сохранялись при сериализации объекта этого типа. Однако, когда он сериализуется только с "a", я получаю следующий xml

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

Как настроить это только для получения xml для свойств, отличных от null? Желаемый результат будет

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

Я хочу исключить эти нулевые значения, потому что будет несколько свойств, и это будет храниться в базе данных (да, это не мой вызов), поэтому я хочу сохранить неиспользованные данные минимальными.

4b9b3361

Ответ 1

Я предполагаю, что вы можете создать XmlWriter, который отфильтровывает все элементы с атрибутом xsi: nil и передает все остальные вызовы основному истинному писателю.

Ответ 2

Вы игнорируете определенные элементы с спецификацией

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} будут указывать сериализатору, если он должен сериализовать соответствующие поля или нет, вернув true/false.

Ответ 3

Лучше поздно, чем никогда...

Я нашел способ (возможно, доступен только с последней структурой, которую я не знаю), чтобы сделать это. Я использовал атрибут DataMember для контракта webservice WCF, и я пометил свой объект следующим образом:

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

Ответ 5

Еще одно решение: regex для спасения, используйте \s+<\w+ xsi:nil="true" \/> чтобы удалить все свойства null из строки, содержащей 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;
}

Ответ 6

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) Удалите Nil

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

Ответ 7

Этот вопрос задан довольно давно, но все еще очень важен даже в 2017 году. Ни один из предложенных ответов здесь не был удовлетворительным для меня, поэтому здесь было простое решение, которое я придумал:

Использование регулярных выражений - это ключ. Поскольку мы не имеем большого контроля над поведением XmlSerializer, поэтому не будем пытаться предотвратить его сериализацию этих типов значений с нулевым значением. Вместо этого просто возьмите сериализованный вывод и замените ненужные элементы пустой строкой с помощью Regex. Используемый шаблон (в С#):

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

Надеюсь, это поможет.

Ответ 8

Самый простой способ написания такого кода, где важно точный вывод, - это:

  • Напишите XML-схему, описывающую ваш желаемый формат.
  • Преобразуйте свою схему в класс с помощью xsd.exe.
  • Преобразуйте свой класс обратно в схему (с помощью xsd.exe снова) и проверьте его на исходную схему, чтобы убедиться, что сериализатор правильно воспроизвел каждое ваше поведение.

Подтвердите и повторите пока у вас нет рабочего кода.

Если вы не уверены в том, какие именно типы данных будут использоваться первоначально, начните с шага 3 вместо шага 1, а затем настройте.

IIRC, для вашего примера вы почти наверняка закончите с Specified свойствами, как вы уже описали, но с их сгенерированными для вас уверенными ударами, пишущими их вручную.: -)

Ответ 9

Отметьте элемент с помощью [XmlElement ( "elementName", IsNullable = false)] нулевые значения будут опущены.