Как исключить нулевые свойства при использовании XmlSerializer - PullRequest
31 голосов
/ 07 октября 2009

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

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 для ненулевых свойств?Желаемый результат будет

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

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

Ответы [ 10 ]

32 голосов
/ 07 октября 2009

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

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.

6 голосов
/ 07 октября 2009

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

4 голосов
/ 07 апреля 2014

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

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

[DataMember(EmitDefaultValue = false)]
public decimal? RentPrice { get; set; }
3 голосов
/ 06 февраля 2017

Еще одно решение: regex для спасения, используйте \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;
}
3 голосов
/ 07 октября 2009
2 голосов
/ 15 мая 2017

Этот вопрос задавался довольно давно, но все еще ОЧЕНЬ актуален даже в 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 голосов
/ 09 февраля 2017

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);
0 голосов
/ 25 марта 2019

Если вы сделаете класс, который вы хотите сериализовать, реализующим 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 голосов
/ 21 декабря 2012

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

0 голосов
/ 07 октября 2009

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

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

Настройте и повторяйте, пока не получите рабочий код.

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

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

...