Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XML deserialization crashes on decimal parse due to formatting

I get a System.FormatException thrown when i try to parse XML into an object. As far as I can tell, it's due to the culture used in System.Xml.Serialization.XmlSerializer.Deserialize, wich expects a dot as the decimal character, but the xml contains a comma.

The object looks as follows:


public sealed class Transaction
{
    [XmlElement("transactionDate")]
    public DateTime TransactionDate { get; set; }

    [XmlElement("transactionAmount")]
    public decimal Amount { get; set; }

    [XmlElement("transactionDescription")]
    public string Description { get; set; }

    [XmlElement("transactionType")]
    public int Type { get; set; }

    public static Transaction FromXmlString(string xmlString)
    {
        var reader = new StringReader(xmlString);
        var serializer = new XmlSerializer(typeof(Transaction));
        var instance = (Transaction) serializer.Deserialize(reader);

        return instance;
    }
}

The xml:


<transaction>
    <transactionDate> 2013-07-02 <transactionDate>
    <transactionAmount>-459,00</transactionAmount>
    <transactionDescription>description</transactionDescription>
    <transactionType>1</transactionType>
</transaction>

I've made it work by introducing a second property that parses the first using my own culture:


namespace MyNamespace
{
    [XmlRoot("transaction"), XmlType("Transaction")]
    public sealed class Transaction
    {
        [XmlElement("transactionDate")]
        public DateTime TransactionDate { get; set; }

        [XmlElement("transactionAmount")]
        public string Amount { get; set; }

        public decimal AmountAsDecimal {
            get
            {
                decimal value;
                Decimal.TryParse(Amount, NumberStyles.Any, CultureInfo.CreateSpecificCulture("sv-SE"), out value);
                return value;
            }
        }

        [XmlElement("transactionDescription")]
        public string Description { get; set; }

        [XmlElement("transactionType")]
        public int Type { get; set; }

        public static Transaction FromXmlString(string xmlString)
        {
            var reader = new StringReader(xmlString);
            var serializer = new XmlSerializer(typeof(Transaction));
            var instance = (Transaction) serializer.Deserialize(reader);

            return instance;
        }
    }
}


which exposes an extra property that i don't want there.

So my question is: is there another way to do this, without iterating over each element and parsing/assigning it to the object "manually"?

like image 282
Softnux Avatar asked Jul 03 '13 00:07

Softnux


2 Answers

XML serializer uses a standardized Number and DateTime format, the standard is defined in the W3C schema datatype specification http://www.w3.org/TR/xmlschema-2/.

Don't expect XmlSerializer to pay attention to the thread's CultureInfo, it intentionally uses a standardized format to ensure you can serialize/deserialize independent of the culture/locale.

like image 82
Kirill Polishchuk Avatar answered Sep 27 '22 17:09

Kirill Polishchuk


What you can do instead is have a property that will be used to serialize/deserialize the decimal.

See: Partially deserialize XML to Object

[XmlType("transaction")]
public sealed class Transaction
{
    [XmlElement("transactionDate")]
    public DateTime TransactionDate { get; set; }

    [XmlIgnore]
    public decimal Amount { get; set; }

    [XmlElement("transactionAmount")]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public string AmountSerialized
    {
        get
        {
            return Amount.ToString(CultureInfo.CreateSpecificCulture("sv-SE"));
        }
        set
        {
            decimal amount;
            Decimal.TryParse(value, NumberStyles.Any, CultureInfo.CreateSpecificCulture("sv-SE"), out amount);
            Amount = amount;
        }
    }

    [XmlElement("transactionDescription")]
    public string Description { get; set; }

    [XmlElement("transactionType")]
    public int Type { get; set; }

    public static Transaction FromXmlString(string xmlString)
    {
        var reader = new StringReader(xmlString);
        var serializer = new XmlSerializer(typeof(Transaction));
        var instance = (Transaction) serializer.Deserialize(reader);

        return instance;
    }
}

This way you can get/set the Amount without needing to worry about how it is serialized. Since this is a DTO you can create another class without the AmountSerialized as your domain object (and use something like AutoMapper to make conversion painless).

Usage:

var data = @"<transaction>
                <transactionDate>2013-07-02</transactionDate>
                <transactionAmount>-459,00</transactionAmount>
                <transactionDescription>description</transactionDescription>
                <transactionType>1</transactionType>
            </transaction>";

var serializer = new XmlSerializer(typeof(Transaction));

using(var stream = new StringReader(data))
using(var reader = XmlReader.Create(stream))
{
     Console.Write(serializer.Deserialize(reader));
}

Also there was a typo in the ending tag for transactionDate.

like image 34
Dustin Kingen Avatar answered Sep 27 '22 18:09

Dustin Kingen