Handling decimal values in Newtonsoft.Json

后端 未结 3 983
日久生厌
日久生厌 2020-11-28 08:04

Edit: It\'s been almost 5 years and I don\'t think this is the way to go. The client should post the data in the correct numerical format. With current fram

相关标签:
3条回答
  • 2020-11-28 08:09

    As an extension to Kwaazaar's answer, I have added the reverse way to the converter as well (In his example it throws a 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);
            }
        }
    }
    

    To plug this into your binder, just add an instance of the converter to the Converters list in the JsonSerializerSettings object:

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

    or

    JsonSerializerSettings settings = new JsonSerializerSettings
    {
        // Some other settings.
        Converters = new List<JsonConverter> { DecimalConverter.Instance }
    };
    
    0 讨论(0)
  • 2020-11-28 08:21

    You can handle both formats (the JSON number representation and the masked string format) using a custom JsonConverter class like this.

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

    To plug this into your binder, just add an instance of the converter to the Converters list in the JsonSerializerSettings object:

    JsonSerializerSettings settings = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        MissingMemberHandling = MissingMemberHandling.Ignore,
        Formatting = Formatting.None,
        DateFormatHandling = DateFormatHandling.IsoDateFormat,
        Converters = new List<JsonConverter> { new DecimalConverter() }
    };
    
    0 讨论(0)
  • 2020-11-28 08:27

    Thanx a lot! I was looking for a solution to make decimals always serialize in a similar manner and this post sent me in the right direction. This is my code:

        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);
            }
        }
    
    0 讨论(0)
提交回复
热议问题