Десериализация пустого значения атрибута xml в свойство nullable int с помощью XmlSerializer


Я получаю xml от третьей стороны, и мне нужно десериализовать его в объект C#. Этот xml может содержать атрибуты со значением целочисленного типа или пустым значением: attr="11" или attr="". Я хочу, чтобы десериализовать значение этого атрибута в свойство с типом, допускающим значение null целое. Но XmlSerializer не поддерживает десериализацию в nullable типы. Следующий тестовый код не выполняется во время создания с помощью XmlSerializer исключение InvalidOperationException {"произошла ошибка, отражающие тип 'TestConsoleApplication.Сериализация'."}.

[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).

может ли кто-нибудь посоветовать, как десериализовать атрибут с пустым значением в тип nullable (как null) в то же время десериализовать непустое значение атрибута в целое число? Есть ли какой-нибудь трюк для этого, так что мне не придется делать десериализация каждого поля вручную (на самом деле их очень много)?

обновление после комментария от 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. пользовательский класс с нулевым значением, как в этом блоге Алекса Скорделлиса

    Я попытался принять класс из этого сообщения в своем блоге проблема:

    [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 для всего класса элементов (он большой), и нет простого обходного пути...

4 64

4 ответа:

Это должно работать:

[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?); }
}

Я решил эту проблему, реализовав интерфейс 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);
    }
}

Я много возился с сериализацией в последнее время и нашел следующие статьи и сообщения полезными при работе с нулевыми данными для типов значений.

ответ как сделать тип, допускающий значение null значение, с помощью которого XmlSerializer в C# - сериализация детали довольно изящный трюк, с помощью которого XmlSerializer. В частности, XmlSerialier ищет XXXSpecified логическое свойство, чтобы определить, если он должен быть включен, что позволяет игнорировать значения null.

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

документация MSDN на Xsi: поддержка привязки атрибутов nil тоже полезно. Как и документация на IXmlSerializable Интерфейс, хотя и пишу свой собственная реализация должна быть вашим последним средством.

подумал, что я мог бы также бросить свой ответ в шляпу: Решена эта проблема путем создания пользовательского типа, реализующего интерфейс 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));
        }
    }
}