Search code examples
c#jsonjson.netdeserialization

Handling invalid inputs when deserializing JSON to decimal values


I have a JSON file with an array of objects, each containing a string value, grade, that I'd like to parse to decimal.

The string value contains a valid decimal about 99% percent of the time, but in that 1%, I'm getting values such as "grade":"<1" which is obviously not a valid decimal. The grade property is about 1 of 100 properties that can sometimes be set to "<1".

This of course throws the following error:

Newtonsoft.Json.JsonReaderException: 'Could not convert string to decimal'

Here is how I am currently deserializing my JSON:

public static Product FromJson(string json) => JsonConvert.DeserializeObject<Product>(json, Converter.Settings);

Is there anything I can do to handle cases where I'm getting those pesky "<1" values? Hopefully something that does the following: if attempting to deserialize a value to decimal, and if the value cannot be parsed to decimal, default to zero.

Any ideas if this is possible? I obviously don't want to have to update my table columns to switch all values from decimal to varchar, because that just sucks and is going to require decimal <-> varchar conversions every time someone wants to query my data.


Solution

  • You can solve this problem by making a custom JsonConverter to handle the decimals:

    class TolerantDecimalConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(decimal);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Integer)
            {
                return Convert.ToDecimal(reader.Value);
            }
            if (reader.TokenType == JsonToken.String && decimal.TryParse((string)reader.Value, out decimal d))
            {
                return d;
            }
            return 0.0m;
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    To use the converter, just add an instance to the Converters collection in the JsonSerializerSettings that you are passing to JsonConvert.DeserializeObject<T>.

    Settings.Converters.Add(new TolerantDecimalConverter());
    

    Note: since you are using decimals, you should probably also set FloatParseHandling to Decimal if you are not already; the default is Double.

    Settings.FloatParseHandling = FloatParseHandling.Decimal;
    

    Working demo here: https://dotnetfiddle.net/I4n00o