22

I'm serializing decimals using Newtonsoft.Json.JsonSerializer.

How can I set it to serialize decimal numbers with only 1 decimal place to use 0 at the end.

i.e. 3.5 serializes to "3.50"?

Chris
  • 7,996
  • 11
  • 66
  • 98

3 Answers3

43

You'll have to write your own custom JsonConverter and use it to intercept the decimal type so you can change how it gets serialized. Here's an example:

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

    public override void WriteJson(JsonWriter writer, object value, 
                                   JsonSerializer serializer)
    {
        writer.WriteValue(string.Format("{0:N2}", value));
    }

    public override bool CanRead
    {
        get { return false; }
    }

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

To use it, simply pass in a new instance of this custom converter to the SerializeObject method:

var json = JsonConvert.SerializeObject(yourObject, new DecimalFormatConverter());
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Cᴏʀʏ
  • 105,112
  • 20
  • 162
  • 194
  • 2
    Note that the culture should be specified in the string.Format. Otherwise you might get weird 'bugs'. – David De Sloovere Aug 25 '14 at 10:30
  • 13
    might be useful to someone: if you do not want to output the double quotes around the value you can use `WriteRawValue` instead – Răzvan Flavius Panda Dec 10 '14 at 11:04
  • 1
    {0:N2} will include commas. If you use WriteRawValue and commas are part of the string you will receive an error. – Karson Jul 06 '17 at 14:28
  • 1
    bear in mind to format the string with CultureInfo.InvariantCulture ```writer.WriteValue(string.Format(CultureInfo.InvariantCulture, "{0:N2}", value));``` – belchev Oct 30 '20 at 16:34
31

The accepted answer is correct, but expanding upon the comments on accepted answer:

If you want the decimals in your JSON to be numbers instead of strings, you need to use WriteRawValue and use :0.00 instead of :N2 for the string formatting (as N2 includes thousand separator commas and other culture specific number formatting that will break your JSON)

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteRawValue($"{value:0.00}");
    }

    public override bool CanRead => false;

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

This is the difference in output compared to the accepted answer.

# writer.WriteRawValue($"{value:0.00}");
{
    "MyDecimal": 3.50,
    "MyBiggerDecimal": 12345.50
}

# writer.WriteValue($"{value:N2}");
{
    "MyDecimal": "3.50",
    "MyBiggerDecimal": "12,345.50"
}

Note - the accepted answer is correct for the OP's specific question i.e. serialize 3.5 to "3.50", but I got here wanting to serialize 3.5 to 3.50 (without the string quotes).

Erresen
  • 1,923
  • 1
  • 22
  • 41
  • 2
    https://stackoverflow.com/a/39526179/4805491 That answer is using `number.ToString(CultureInfo.InvariantCulture)`. Is it right way? – Denis535 Jun 11 '19 at 08:33
  • 3
    Yes, you should always explicitly specify InvariantCulture in .NET if your output is intended for machine to machine communication, which JSON is for by nature. If you don't, the result will be formatted according to whatever culture the code is running in. E.g. on a Dutch server, the accepted answer will return "3,50", not "3.50". – Martin Watts Mar 11 '20 at 13:15
  • 1
    bear in mind to use invariant culture: ```writer.WriteRawValue(FormattableString.Invariant($"{value:0.00}"));``` – belchev Oct 30 '20 at 16:41
  • 2
    Also if your model has property of type decimal? (nullable) you need to update CanConvert method to `return objectType == typeof(decimal) || objectType == typeof(decimal?);`, otherwise it will not work with nullable properties. – Andrew Basarab Jun 07 '21 at 09:05
  • Is it possible when using `System.Text` rather than newtonsoft? https://stackoverflow.com/questions/71255259/why-is-system-text-json-converter-not-respecting-my-decimal-format?noredirect=1#comment125952206_71255259 – Bassie Feb 24 '22 at 16:55
0

Hy! The second answer is also correct, but not reflecting Cultures. If you want to have really 0.00 (with .) you have to use:

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        FormattableString formattableString = $"{value:0.00}";
        writer.WriteRawValue(formattableString.ToString(CultureInfo.InvariantCulture));
    }

    public override bool CanRead => false;

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

With this it is garantueed to have dot as decimal separater.

The difference is:

    FormattableString formattableString = $"{value:0.00}";
    writer.WriteRawValue(formattableString.ToString(CultureInfo.InvariantCulture));