Как я могу заставить XmlSerializer кодировать bools как да / нет? - PullRequest
15 голосов
/ 09 марта 2009

Я отправляю xml другой программе, которая ожидает логические флаги как «да» или «нет», а не как «истина» или «ложь».

У меня есть класс, определенный как:

[XmlRoot()]
public class Foo {
    public bool Bar { get; set; }
}

Когда я сериализую его, мой вывод будет выглядеть так:

<Foo><Bar>true</Bar></Foo>

Но я бы хотел, чтобы это было так:

<Foo><Bar>yes</Bar></Foo>

Могу ли я сделать это во время сериализации? Я бы предпочел не прибегать к этому:

[XmlRoot()]
public class Foo {
    [XmlIgnore()]
    public bool Bar { get; set; }

    [XmlElement("Bar")]
    public string BarXml { get { return (Bar) ? "yes" : "no"; } }
}

Обратите внимание, что я также хочу иметь возможность десериализовать эти данные обратно.

Ответы [ 7 ]

22 голосов
/ 09 марта 2009

Хорошо, я изучал это еще немного. Вот что я придумала:

// use this instead of a bool, and it will serialize to "yes" or "no"
// minimal example, not very robust
public struct YesNo : IXmlSerializable {

    // we're just wrapping a bool
    private bool Value;

    // allow implicit casts to/from bool
    public static implicit operator bool(YesNo yn) {
        return yn.Value;
    }
    public static implicit operator YesNo(bool b) {
        return new YesNo() {Value = b};
    }

    // implement IXmlSerializable
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader) {
        Value = (reader.ReadElementContentAsString() == "yes");
    }
    public void WriteXml(XmlWriter writer) {
        writer.WriteString((Value) ? "yes" : "no");
    }
}

Затем я изменяю свой класс Foo следующим образом:

[XmlRoot()]
public class Foo {      
    public YesNo Bar { get; set; }
}

Обратите внимание, что, поскольку YesNo неявно преобразуется в bool (и наоборот), вы все равно можете сделать это:

Foo foo = new Foo() { Bar = true; };
if ( foo.Bar ) {
   // ... etc

Другими словами, вы можете относиться к нему как к булу.

И w00t! Сериализуется к этому:

<Foo><Bar>yes</Bar></Foo>

Он также правильно десериализуется.

Возможно, есть какой-то способ заставить мой XmlSerializer автоматически приводить любые bool s, с которыми он сталкивается, к YesNo s, как это происходит, - но я пока не нашел его. Кто-нибудь?

8 голосов
/ 09 марта 2009

Очень просто. Используйте суррогатную собственность. Примените XmlIgnore к фактическому свойству. Суррогат является строкой и должен использовать атрибут XmlElement, который принимает переопределение имени элемента. Укажите имя фактического свойства в переопределении. Суррогатное свойство сериализуется по-разному в зависимости от значения фактического свойства. Вы также должны предоставить установщик для суррогата, и установщик должен установить фактическое свойство соответствующим образом для любого значения, которое оно сериализовало. Другими словами это должно идти обоими путями.

Снип:

    public class SomeType 
    {

        [XmlElement]
        public int IntValue;

        [XmlIgnore]
        public bool Value;

        [XmlElement("Value")]
        public string Value_Surrogate {
            get { return (Value)? "Yes, definitely!":"Absolutely NOT!"; }
            set { Value= (value=="Yes, definitely!"); }
        }

    }

нажмите здесь, чтобы получить пример полного скомпилированного источника .

3 голосов
/ 09 марта 2009

Если значение bool сериализовать как «да» или «нет», это вообще изменит тип данных на логический. Вместо этого, вы можете добавить отдельное свойство, которое оценивает логическое значение и возвращает «да» или «нет» в зависимости от типа данных? Возможно, вы могли бы даже принудительно указать «да» или «нет», сделав тип возвращаемого значения перечислением, которое указывает только эти значения.

public YesOrNo DoYouLoveIt
{
    get { return boolToEvaluate ? YesOrNo.Yes : YesOrNo.No; }
}

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

2 голосов
/ 30 августа 2012

Я использую метод свойства, но вместо проверки, чтобы увидеть, равна ли строка да или нет, я предпочитаю проверять, начинается ли строка с (без учета регистра) "YT1". Это позволяет файлу содержать значения true, True, t, T, y, Y, yes, Yes, 1 и т. Д., Все из которых будет иметь значение true. Хотя я могу указать, что false - это false, False, f, F, n, N, no, No, 0 и т. Д., Все, что не соответствует истине, все равно оценивается как false.

0 голосов
/ 26 ноября 2012

@ Blorgbeard: Если у вас есть более одного из этих классов YesNo в классе объекта, не забудьте прочитать весь элемент.

public void ReadXml(XmlReader reader)
{
    string element = reader.ReadOuterXml();
    int startIndex = element.IndexOf('>') + 1;
    int length = element.LastIndexOf('<') - startIndex;

    string text = (element.Substring(startIndex, length).ToLowerInvariant();

    Value = (text == "yes");
}

В противном случае это может вызвать проблемы.

Метод ReadXml должен воссоздать ваш объект, используя информацию, записанную методом WriteXml.

Когда вызывается этот метод, читатель располагается в начале элемента, который оборачивает информацию для вашего типа. То есть просто перед начальным тегом, указывающим на начало сериализованного объект. Когда этот метод возвращается, он должен прочитать весь элемент от начала до конца, включая все его содержимое. в отличие от Метод WriteXml, фреймворк не обрабатывает элемент оболочки автоматически. Ваша реализация должна сделать это. Несоблюдение эти правила позиционирования могут привести к тому, что код сгенерирует непредвиденное время выполнения исключения или поврежденные данные.

0 голосов
/ 09 марта 2009

Ваш пример собственности, вероятно, самый простой способ сделать это. Если это поможет, я считаю, что вам не нужно делать это публичным свойством, поскольку атрибут реализует ISerializable в классе за вашей спиной. Чтобы включить десериализацию, вы должны просто реализовать set { Bar = value == "yes"; }

0 голосов
/ 09 марта 2009

То, что вам нужно сделать, больше похоже на проблему с отображением. Если ваше приложение позволяет, вам лучше сохранить тип данных как логическое значение и отображать Да / Нет в вашем пользовательском интерфейсе.

...