Десериалізація порожнього значення атрибута xml у властивості int, що допускає нуль, за допомогою XmlSerializer


74

Я отримую xml від третьої сторони, і мені потрібно десеріалізувати його в об’єкт C #. Цей xml може містити атрибути зі значенням цілочисельного типу або порожнім значенням: attr = ”11” або attr = ””. Я хочу десеріалізувати це значення атрибута у властивості з типом цілого числа, що обнуляється. Але XmlSerializer не підтримує десеріалізацію для типів, що дозволяють обнуляти. Наступний тестовий код не вдається під час створення XmlSerializer з InvalidOperationException {"Сталася помилка, що відображає тип 'TestConsoleApplication.SerializeMe'."}.

[XmlRoot("root")]
public class SerializeMe
{
    [XmlElement("element")]
    public Element Element { get; set; }
}

public class Element
{
    [XmlAttribute("attr")]
    public int? Value { get; set; }
}

class Program {
    static void Main(string[] args) {
        string xml = "<root><element attr=''>valE</element></root>";
        var deserializer = new XmlSerializer(typeof(SerializeMe));
        Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml));
        var result = (SerializeMe)deserializer.Deserialize(xmlStream);
    }
}

Коли я змінюю тип властивості 'Value' на int, десеріалізація не вдається з InvalidOperationException:

У документі XML є помилка (1, 16).

Хтось може порадити, як десериалізувати атрибут з порожнім значенням у типовий тип (як нульовий), одночасно десеріалізуючи непусте значення атрибута в ціле число? Чи є якась хитрість для цього, тому мені не доведеться виконувати десеріалізацію кожного поля вручну (насправді їх дуже багато)?

Оновлення після коментаря від ahsteele:

  1. Xsi: атрибут nil

    Наскільки мені відомо, цей атрибут працює лише з XmlElementAttribute - цей атрибут вказує, що елемент не має вмісту, будь то дочірні елементи або основний текст. Але мені потрібно знайти рішення для XmlAttributeAttribute. У будь-якому випадку я не можу змінити xml, оскільки я не маю над ним контролю.

  2. bool * Вказане властивість

    Ця властивість працює лише тоді, коли значення атрибута не є порожнім або коли атрибут відсутній. Коли attr має порожнє значення (attr = ''), конструктор XmlSerializer не працює (як очікувалося).

    public class Element
    {
        [XmlAttribute("attr")]
        public int Value { get; set; }
    
        [XmlIgnore]
        public bool ValueSpecified;
    }
    
  3. Спеціальний клас Nullable, як у цьому дописі блогу Алекса Скорделліса

    Я спробував прийняти клас із цього допису в блозі до своєї проблеми:

    [XmlAttribute("attr")]
    public NullableInt Value { get; set; } 
    

    Але конструктор XmlSerializer не працює з InvalidOperationException:

    Не вдається серіалізувати член "Значення" типу TestConsoleApplication.NullableInt.

    XmlAttribute / XmlText не можна використовувати для кодування типів, що реалізують IXmlSerializable}

  4. Потворне сурогатне рішення (соромно за те, що я написав цей код тут :)):

    public class Element
    {
        [XmlAttribute("attr")]
        public string SetValue { get; set; }
    
        public int? GetValue()
        {
            if ( string.IsNullOrEmpty(SetValue) || SetValue.Trim().Length <= 0 )
                return null;
    
            int result;
            if (int.TryParse(SetValue, out result))
                return result;
    
            return null;
        }
    }
    

    Але я не хочу придумувати таке рішення, оскільки воно порушує інтерфейс мого класу для своїх споживачів. Я б краще реалізував інтерфейс IXmlSerializable вручну.

На даний момент, схоже, я повинен впровадити IXmlSerializable для всього класу Element (він великий), і немає простих обхідних шляхів ...

Відповіді:


61

Це має спрацювати:

[XmlIgnore]
public int? Age { get; set; }

[XmlElement("Age")]
public string AgeAsText
{
  get { return (Age.HasValue) ? Age.ToString() : null; } 
  set { Age = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?); }
}

4
Це буде працювати, але це те саме рішення, що і номер 4) з мого запитання. Я не хочу вводити сурогатні поля в загальнодоступний інтерфейс свого класу. Дякую
Аляксей Ключнікаў

9
FWIW, я вважаю, що це рішення є кращим, ніж явна реалізація IXmlSerializable (прийняте рішення), хоча це не стосується конкретного питання OP. Я уникаю впровадження IXmlSerializable, якщо це абсолютно не потрібно, виявляючи, що це змушує мене коштувати дорожче в обслуговуванні протягом тривалого часу. У такому простому випадку, як цей, і без будь-яких інших пом’якшувальних факторів, я піду на «потворне» сурогатне рішення, не замислюючись над цим.
Пол Преветт,

дає трохи додаткових накладних витрат, але ви, звичайно, можете мати два класи - один для десеріалізації, який має всі ці додаткові властивості, а другий, який має лише фактичні значення. Створіть неявне перетворення, яке просто повертає новий екземпляр класу, в який перетворюється, з усією правильною інформацією.
TheHitchenator

21

Я вирішив цю проблему, застосувавши інтерфейс IXmlSerializable. Я не знайшов простішого шляху.

Ось зразок тестового коду:

[XmlRoot("root")]
public class DeserializeMe {
    [XmlArray("elements"), XmlArrayItem("element")]
    public List<Element> Element { get; set; }
}

public class Element : IXmlSerializable {
    public int? Value1 { get; private set; }
    public float? Value2 { get; private set; }

    public void ReadXml(XmlReader reader) {
        string attr1 = reader.GetAttribute("attr");
        string attr2 = reader.GetAttribute("attr2");
        reader.Read();

        Value1 = ConvertToNullable<int>(attr1);
        Value2 = ConvertToNullable<float>(attr2);
    }

    private static T? ConvertToNullable<T>(string inputValue) where T : struct {
        if ( string.IsNullOrEmpty(inputValue) || inputValue.Trim().Length == 0 ) {
            return null;
        }

        try {
            TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
            return (T)conv.ConvertFrom(inputValue);
        }
        catch ( NotSupportedException ) {
            // The conversion cannot be performed
            return null;
        }
    }

    public XmlSchema GetSchema() { return null; }
    public void WriteXml(XmlWriter writer) { throw new NotImplementedException(); }
}

class TestProgram {
    public static void Main(string[] args) {
        string xml = @"<root><elements><element attr='11' attr2='11.3'/><element attr='' attr2=''/></elements></root>";
        XmlSerializer deserializer = new XmlSerializer(typeof(DeserializeMe));
        Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml));
        var result = (DeserializeMe)deserializer.Deserialize(xmlStream);
    }
}

12

Я сам багато пізно возився з серіалізацією і виявив, що наступні статті та дописи корисні при роботі з нульовими даними для типів значень.

Відповідь на тему Як зробити тип значення нульовим за допомогою XmlSerializer в C # - серіалізація детально описує досить вигадливий фокус XmlSerializer. Зокрема, XmlSerialier шукає логічне властивість XXXSpecified, щоб визначити, чи слід його включати, що дозволяє ігнорувати нульові значення.

Алекс Скорделліс задав запитання StackOverflow, яке отримало хорошу відповідь . Алекс також добре написав у своєму блозі про проблему, яку він намагався вирішити, використовуючи XmlSerializer для десеріалізації в Nullable <int> .

Документація MSDN щодо Xsi: nil Підтримка прив'язки атрибутів також корисна. Як і документація щодо інтерфейсу IXmlSerializable , хоча написання власної реалізації має бути вашим останнім засобом.


1
Посилання "Використання XmlSerializer для десеріалізації в Nullable" припинено. Ось кешована версія від google
Anttu

@Anttu Я переключив посилання у відповіді на архів Wayback Machine оригіналу за допомогою XmlSerializer для десеріалізації в Nullable <int> .
ahsteele

2

Думав, що я міг би також кинути свою відповідь у капелюх: Вирішив цю проблему, створивши власний тип, який реалізує інтерфейс IXmlSerializable:

Скажімо, у вас є об’єкт XML із такими вузлами:

<ItemOne>10</Item2>
<ItemTwo />

Об'єкт, який їх представляє:

public class MyItems {
    [XmlElement("ItemOne")]
    public int ItemOne { get; set; }

    [XmlElement("ItemTwo")]
    public CustomNullable<int> ItemTwo { get; set; } // will throw exception if empty element and type is int
}

Динамічна нульова структура, яка представляє будь-які потенційні записи, що допускають нуль, разом із перетворенням

public struct CustomNullable<T> : IXmlSerializable where T: struct {
    private T value;
    private bool hasValue;

    public bool HasValue {
        get { return hasValue; }
    }

    public T Value {
        get { return value; }
    }

    private CustomNullable(T value) {
        this.hasValue = true;
        this.value = value;
    }

    public XmlSchema GetSchema() {
        return null;
    }

    public void ReadXml(XmlReader reader) {
        string strValue = reader.ReadString();
        if (String.IsNullOrEmpty(strValue)) {
            this.hasValue = false;
        }
        else {
            T convertedValue = strValue.To<T>();
            this.value = convertedValue;
            this.hasValue = true;
        }
        reader.ReadEndElement();

    }

    public void WriteXml(XmlWriter writer) {
        throw new NotImplementedException();
    }

    public static implicit operator CustomNullable<T>(T value) {
        return new CustomNullable<T>(value);
    }

}

public static class ObjectExtensions {
    public static T To<T>(this object value) {
        Type t = typeof(T);
        // Get the type that was made nullable.
        Type valueType = Nullable.GetUnderlyingType(typeof(T));
        if (valueType != null) {
            // Nullable type.
            if (value == null) {
                // you may want to do something different here.
                return default(T);
            }
            else {
                // Convert to the value type.
                object result = Convert.ChangeType(value, valueType);
                // Cast the value type to the nullable type.
                return (T)result;
            }
        }
        else {
            // Not nullable.
            return (T)Convert.ChangeType(value, typeof(T));
        }
    }
}

1

Ви також можете зробити це, завантаживши xmlв, XmlDocumentа потім десеріалізуючи це, Jsonщоб отримати об'єкт, Tякий ви шукаєте.

        public static T XmlToModel<T>(string xml)
        {

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);

            string jsonText = JsonConvert.SerializeXmlNode(doc);

            T result = JsonConvert.DeserializeObject<T>(jsonText);

            return result;
        }


Копіювати та вставити відповідь із посилання
CrudaLilium
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.