在 Newtonsoft.Json 中处理十进制值

IT技术 c# javascript json json.net deserialization
2021-03-06 08:32:51

编辑:已经快 5 年了,我不认为这是要走的路。客户应以正确的数字格式发布数据。使用 React 或 Angular 等当前框架,或者具有适当的架构和错误处理和验证,我认为这几乎不是问题。

但如果有人希望展示他们的 Json.NET 肌肉,请随时查看答案。


我有一个 MVC 应用程序,我在其中处理了一些 JSON。这很简单。我的 ModelBinder 中有一段简单的代码:

return JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType, new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    Formatting = Formatting.None,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    FloatParseHandling = FloatParseHandling.Decimal
});

它完美无缺。

嗯,有点。

假设我有这门课:

public class MyClass
{
    public decimal MyProp { get; set; }
}

如果我尝试反序列化这个 json:

"{\"MyProp\": 9888.77}"

当然它有效,因为它9888.77是一个 Javascript 浮点值。我认为。

但是我的页面中有一个掩码的钱输入,使 JSON 看起来像这样(对不起我的英语):

"{\"MyProp\": \"9.888,77\" }"

AAAND,它失败了。它说它Could not convert string to decimal

好吧,这很公平。它不是 JS 浮点数,而是Convert.ToDecimal("9.888,77")按照我想要的方式工作。

我已经在互联网上阅读了一些关于自定义反序列化器的教程,但是我无法为应用程序中的每个类定义一个自定义反序列化器。

我想要的是简单地重新定义 JSON.Net 将字符串转换为十进制属性的方式,在我想要反序列化的任何类中。我想Convert.ToDecimal在转换小数的过程中注入该函数,当当前转换器不起作用时。

有什么办法可以做到吗?

我认为有一种方法可以做到这一点,所以我稍微更改了我的代码。

JsonSerializer serializer = new JsonSerializer
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    Formatting = Formatting.None,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    FloatParseHandling = FloatParseHandling.Decimal,
};



return serializer.Deserialize(new DecimalReader(jsonStr), bindingContext.ModelType);

并创建了这个类:

public class DecimalReader : JsonTextReader
{
    public DecimalReader(string s)
        : base(new StringReader(s))
    {
    }

    public override decimal? ReadAsDecimal()
    {
        try
        {
            return base.ReadAsDecimal();
        }
        catch (Exception)
        {
            if (this.TokenType == JsonToken.String)
            {
                decimal value = 0;

                bool convertible = Decimal.TryParse(this.Value.ToString(), out value);

                if (convertible)
                {
                    return new Nullable<decimal>(value);
                }
                else { throw; }
            }
            else
            {
                throw;
            }
        }
    }
}

但它非常难看:它只在崩溃时执行我想要的,并且依赖base.ReadAsDecimal()crashing不能更丑了。

并且不起作用Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y.

值本身正在被转换,但也许出于某种原因,它仍然尝试将字符串“1.231,23”转换为十进制。

那么,有没有办法正确地做到这一点?

3个回答

您可以使用这样的自定义JsonConverter来处理这两种格式(JSON 数字表示形式和掩码字符串格式)

class DecimalConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(decimal) || objectType == typeof(decimal?));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer)
        {
            return token.ToObject<decimal>();
        }
        if (token.Type == JTokenType.String)
        {
            // customize this to suit your needs
            return Decimal.Parse(token.ToString(), 
                   System.Globalization.CultureInfo.GetCultureInfo("es-ES"));
        }
        if (token.Type == JTokenType.Null && objectType == typeof(decimal?))
        {
            return null;
        }
        throw new JsonSerializationException("Unexpected token type: " + 
                                              token.Type.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

要将其插入到您的活页夹中,只需将转换器的一个实例添加到对象Converters列表中JsonSerializerSettings

JsonSerializerSettings settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore,
    Formatting = Formatting.None,
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    Converters = new List<JsonConverter> { new DecimalConverter() }
};
奇迹般有效。为了确保可空小数的安全返回,我对条件之一进行了以下更改 if (token.Type == JTokenType.String) { Decimal d; if (Decimal.TryParse(token.ToString(), System.Globalization.NumberStyles.AllowParentheses, System.Globalization.CultureInfo.CurrentCulture, out d)) return d; 否则返回空;}
2021-04-28 08:32:51
如果JToken.Load已经将 a 的 JSON 字符串表示形式转换decimal为 afloatInteger然后将其转换为 a ,这是否会导致精度损失问题decimal
2021-05-06 08:32:51

非常感谢!我一直在寻找一种解决方案,使小数始终以类似的方式序列化,这篇文章让我朝着正确的方向前进。这是我的代码:

    internal class DecimalConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(decimal) || objectType == typeof(decimal?));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            Decimal? d = default(Decimal?);
            if (value != null)
            {
                d = value as Decimal?;
                if (d.HasValue) // If value was a decimal?, then this is possible
                {
                    d = new Decimal?(new Decimal(Decimal.ToDouble(d.Value))); // The ToDouble-conversion removes all unnessecary precision
                }
            }
            JToken.FromObject(d).WriteTo(writer);
        }
    }
感谢您发布此内容,谁能想到 2020 年 JsonNewtonSoft 仍然不会为此提供开箱即用的解决方案。谢谢!
2021-04-24 08:32:51

作为 Kwaazaar 答案的扩展,我还向转换器添加了相反的方式(在他的示例中,它抛出一个NotImplementedException.

namespace Something.Converter
{
    using System;

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// <inheritdoc cref="JsonConverter"/>
    /// <summary>
    /// Converts an object to and from JSON.
    /// </summary>
    /// <seealso cref="JsonConverter"/>
    public class DecimalConverter : JsonConverter
    {
        /// <summary>
        /// Gets a new instance of the <see cref="DecimalConverter"/>.
        /// </summary>
        public static readonly DecimalConverter Instance = new DecimalConverter();

        /// <inheritdoc cref="JsonConverter"/>
        /// <summary>
        /// Determines whether this instance can convert the specified object type.
        /// </summary>
        /// <param name="objectType">Type of the object.</param>
        /// <returns>
        ///     <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
        /// </returns>
        /// <seealso cref="JsonConverter"/>
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(decimal) || objectType == typeof(decimal?);
        }

        /// <inheritdoc cref="JsonConverter"/>
        /// <summary>
        /// Reads the JSON representation of the object.
        /// </summary>
        /// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
        /// <param name="objectType">Type of the object.</param>
        /// <param name="existingValue">The existing value of object being read.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <returns>The object value.</returns>
        /// <seealso cref="JsonConverter"/>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (!(reader.Value is string value))
            {
                if (objectType == typeof(decimal?))
                {
                    return null;
                }

                return default(decimal);
            }

            // ReSharper disable once StyleCop.SA1126
            if (decimal.TryParse(value, out var result))
            {
                // ReSharper disable once StyleCop.SA1126
                return result;
            }

            if (objectType == typeof(decimal?))
            {
                return null;
            }

            return default(decimal);
        }

        /// <inheritdoc cref="JsonConverter"/>
        /// <summary>
        /// Writes the JSON representation of the object.
        /// </summary>
        /// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
        /// <param name="value">The value.</param>
        /// <param name="serializer">The calling serializer.</param>
        /// <seealso cref="JsonConverter"/>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var d = default(decimal?);

            if (value != null)
            {
                d = value as decimal?;
                if (d.HasValue)
                {
                    d = new decimal(decimal.ToDouble(d.Value));
                }
            }

            JToken.FromObject(d ?? 0).WriteTo(writer);
        }
    }
}

要将其插入到您的活页夹中,只需将转换器的一个实例添加到 JsonSerializerSettings 对象中的转换器列表中:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    // Some other settings.
    Converters = new List<JsonConverter> { new DecimalConverter() }
};

或者

JsonSerializerSettings settings = new JsonSerializerSettings
{
    // Some other settings.
    Converters = new List<JsonConverter> { DecimalConverter.Instance }
};