Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I deserialize a high-precision decimal value with Json.NET?

I want to deserialize JSON containing long decimal values into custom types to maintain their precision (i.e., a custom BigDecimal class). I'm using Json.NET 9.0.1 and .NET 4.6.1. I've tried using a JsonConverter, but it seems that the value available when ReadJson is called has already been identified and read by Json.NET as a .NET decimal type and is limited to its precision.

Ideally I would have access to the raw string so I could put it in a custom type. I can use string properties on the target object and it deserializes the full string, but then I'd have to further process the object (i.e., copy it into another representation) and that's especially messy across a large schema.

Any thoughts on a better approach?


Here's the target class:

public class DecimalTest
{
    public string stringValue { get; set; }
    public decimal decimalValue { get; set; }
    public BigDecimal bigDecimalValue { get; set; }
}

Here's a test with JSON:

    [TestMethod]
    public void ReadBigDecimal_Test()
    {
        var json = @"{
            ""stringValue"" : 0.0050000012852251529693603515625,
            ""decimalValue"" : 0.0050000012852251529693603515625,
            ""bigDecimalValue"" : 0.0050000012852251529693603515625
        }";

        var settings = new JsonSerializerSettings();
        settings.FloatParseHandling = FloatParseHandling.Decimal;
        settings.Converters.Add(new JsonBigDecimalConverter());

        var result = JsonConvert.DeserializeObject<DecimalTest>(json, settings);

        Assert.IsNotNull(result);
        Assert.AreEqual("0.0050000012852251529693603515625", result.stringValue);
        Assert.AreEqual(0.0050000012852251529693603516m, result.decimalValue);

        // *** This case fails ***
        Assert.AreEqual("0.0050000012852251529693603515625", result.bigDecimalValue.ToString());
    }

Here's the custom converter:

public class JsonBigDecimalConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(BigDecimal));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // *** reader.Value here already appears to be a .NET decimal.
        // *** If I had access to the raw string I could get this to work.
        return BigDecimal.Parse(reader.Value.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
like image 388
jlalughery Avatar asked Aug 10 '16 05:08

jlalughery


1 Answers

Could you try if the following implementation of ReadJson works as you expect:

public override object ReadJson(
    JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var token = JToken.Load(reader);
    return BigDecimal.Parse(token.ToString());
}

Update

Unfortunately the above won't work. There seems to be no way to read the raw string from the JSON data.

Also note that in my tests the assert for stringValue fails first. See this working example: https://dotnetfiddle.net/s0pqg3

I assume this is because Json.NET internally immediately parses any number token it encounters according to the specified FloatParseHandling. The raw data is never preserved.

I think the only solution is to wrap the big decimal string in quotes like so:

"bigDecimalValue" : "0.0050000012852251529693603515625"

Here is a working example that does exactly that in order to preserve the desired precision: https://dotnetfiddle.net/U1UG3z

like image 129
Good Night Nerd Pride Avatar answered Nov 16 '22 14:11

Good Night Nerd Pride