【问题标题】:XML deserialization crashes on decimal parse due to formatting由于格式化,XML 反序列化在十进制解析时崩溃
【发布时间】:2013-07-03 00:33:58
【问题描述】:

当我尝试将 XML 解析为对象时抛出 System.FormatException。据我所知,这是由于 System.Xml.Serialization.XmlSerializer.Deserialize 中使用的文化,它需要一个点作为十进制字符,但 xml 包含一个逗号。

对象如下:


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

xml:

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

我通过引入第二个属性来实现它,该属性使用我自己的文化解析第一个属性:

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

它暴露了一个我不想要的额外属性。

所以我的问题是:是否有另一种方法可以做到这一点,而无需迭代每个元素并“手动”解析/分配给对象?

【问题讨论】:

  • 来自here 听起来XmlSerializer 正在使用this W3C schema 并且应该相对独立于文化以避免跨机器/文化的序列化/反序列化问题。我会你现在正在做的可能是最好的方式(也许用[XmlIgnore] 装饰AmountAsDecimal 只是让它更明显一点)。无论如何,只要您的序列化对象是纯粹的数据传输对象并从您的应用程序/业务逻辑中抽象出来,那么我希望它不会造成太大伤害。
  • 但在第二次阅读时,您可能想要翻转哪些属性解析和哪些序列化。将public decimal Amount 公开为您将在代码 API 中正常获取/设置的值,但将其作为[XmlIgnore]。然后有一个 public string SerializedAmount 属性,其 get/set 实现将格式化/解析您的专业文化。至少这样,您用于写入 Transaction 对象的 API 不必考虑如何格式化字符串;它只是写入一个decimal 值。如果您编写它的方式(例如您更改 SerializedAmount 属性),那么您的代码并不关心。

标签: c# .net xml serialization xml-deserialization


【解决方案1】:

XML 序列化程序使用标准化的 Number 和 DateTime 格式,该标准在 W3C 模式数据类型规范 http://www.w3.org/TR/xmlschema-2/ 中定义。

不要指望XmlSerializer 关注线程的CultureInfo,它有意使用标准化格式来确保您可以独立于文化/语言环境进行序列化/反序列化。

【讨论】:

  • 即便如此,从我的查询返回的 XML 包含带逗号的双精度值。
  • @Softnux,我认为您找到了一种可能的解决方案。
  • +1。 @Softnux“我的查询”如“您创建此 XML”或“您针对第三方服务执行的查询”?如果是前者,我强烈建议对数字/日期时间值使用正确的 XML 格式。从长远来看会容易得多。
  • 是的,它来自第三方,我无意再次序列化。
  • 我的问题与我们实施序列化之前创建的 XML 有关。 (是的,在文本级别为 XML IO 编写了一堆代码)。为了保持这种兼容性,我将所有数据序列化为字符串并自行处理格式问题。丑陋而笨拙,但它起作用了。我用一个桥接类封装了格式化问题,将应用程序内部状态与通过 XML IO 获得的状态桥接起来。
【解决方案2】:

您可以做的是拥有一个用于序列化/反序列化decimal 的属性。

见: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;
    }
}

这样您就可以获取/设置Amount,而无需担心它是如何序列化的。由于这是一个 DTO,您可以创建另一个没有 AmountSerialized 作为域对象的类(并使用 AutoMapper 之类的东西来使转换变得轻松)。

用法:

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

transactionDate 的结束标记也有错字。

【讨论】:

    【解决方案3】:

    如果您知道生成 XML 的文化,一个简单的解决方案是在反序列化之前将当前线程的文化切换到该文化。

        System.Globalization.CultureInfo oCurrentCulture = null;
        try
        {
            // Save the current culture
            oCurrentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
            System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE");
    
            // Do your work
        }
        finally
        {
                        // Restore the saved culture
            System.Threading.Thread.CurrentThread.CurrentCulture = oCurrentCulture;
        }
    

    【讨论】:

    • 我发现最佳实践是在尝试(或使用)中使用最少的代码。由于保存文化没有必要在尝试保存线程的文化之前是合适的
    猜你喜欢
    • 1970-01-01
    • 2011-09-24
    • 2023-04-09
    • 1970-01-01
    • 2015-09-22
    • 1970-01-01
    • 2012-05-06
    • 2010-12-09
    • 1970-01-01
    相关资源
    最近更新 更多