1. ホーム
  2. .net

[解決済み] XmlSerializerを使用して、空のxml属性値をnullable intプロパティにデシリアライズする。

2023-06-25 12:36:45

質問

サードパーティからxmlを入手し、C#オブジェクトにデシリアライズする必要があります。このxmlは、整数型の値または空の値:attr="11 "またはattr=""を持つ属性を含む場合があります。この属性値を、nullable integer型のプロパティにデシリアライズしたい。しかし、XmlSerializerはnullable型へのデシリアライズをサポートしていません。以下のテストコードは、InvalidOperationException {"タイプ 'TestConsoleApplication.SerializeMe' を反映するエラーが発生し、XmlSerializer の作成時に失敗します。}

[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. Alex Scordellisによるこのブログ記事のような、カスタムNullableクラス。

    このブログの投稿にあるクラスを私の問題に採用することを試みました。

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

    しかし、XmlSerializerのコンストラクタはInvalidOperationExceptionで失敗しています。

    TestConsoleApplication.NullableInt型のメンバ 'Value' をシリアライズすることができません。

    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インターフェイスを手動で実装したほうがよさそうです。

現在のところ、Elementクラス全体に対してIXmlSerializableを実装しなければならないようで(大きいです)、簡単な回避策はないようです...。

どのように解決するのですか?

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