C# – How to force a minimum number of decimal places in Json.net

cjsonjson.net

I'm getting an annoying inconsistency when I'm writing decimals to json using json.net. Sometimes it's to 1 dp, other times 2.

Obviously I'm aware of solutions to output decimals to strings with a certain number of decimals such as this, but you don't have that control using json.net without writing a custom serializer I guess.

I am also aware of Math.Round to enforce a maximum number of decimal places, this question relates to enforcing a minimum number of decimal places.

The first two tests show what is happening, it is keeping the original number of decimal places from the declaration or calculation.

I found I can add and then subtract a small fraction which the next two tests show working, but is there a cleaner way?

[TestFixture]
public sealed class DecimalPlaces
{
    public class JsonType
    {
        public decimal Value { get; set; }
    }

    [Test]
    public void TwoDp()
    {
        var obj = new JsonType { Value = 1.00m };
        Assert.AreEqual("{\"Value\":1.00}", JsonConvert.SerializeObject(obj));
    }

    [Test]
    public void OneDp()
    {
        var json = new JsonType { Value = 1.0m };
        Assert.AreEqual("{\"Value\":1.0}", JsonConvert.SerializeObject(obj));
    }

    private decimal ForceMinimumDp(decimal p, int minDecimalPlaces)
    {
        decimal smallFrac = 1m/((decimal)Math.Pow(10, minDecimalPlaces));
        return p + smallFrac - smallFrac;
    }

    [Test]
    public void ForceMinimumTwoDp()
    {
        var obj = new JsonType { Value = ForceMinimumDp(1.0m, 2) };
        Assert.AreEqual("{\"Value\":1.00}", JsonConvert.SerializeObject(obj));
    }

    [Test]
    public void ForceMinimumThreeDp()
    {
        var obj = new JsonType { Value = ForceMinimumDp(1.0m, 3) };
        Assert.AreEqual("{\"Value\":1.000}", JsonConvert.SerializeObject(obj));
    }
}

Best Answer

You can do it with a custom JSON converter:

class DecimalJsonConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return 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)
    {
        writer.WriteRawValue(((decimal) value).ToString("F2", CultureInfo.InvariantCulture));
    }
}

This is a very basic converter. You may need to extend it to support other floating-point types, or perhaps even integer types too.

Now instantiate your serialiser and pass it your custom converter, like so:

var serializer = new JsonSerializer();
serializer.Converters.Add(new DecimalJsonConverter());