Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing a decimal to JSON, how to round off?

I have a class

public class Money
{
    public string Currency { get; set; }
    public decimal Amount { get; set; }
}

and would like to serialize it to JSON. If I use the JavaScriptSerializer I get

{"Currency":"USD","Amount":100.31000}

Because of the API I have to conform to needs JSON amounts with maximum two decimal places, I feel it should be possible to somehow alter the way the JavaScriptSerializer serializes a decimal field, but I can't find out how. There is the SimpleTypeResolver you can pass in the constructor, but it only work on types as far as I can understand. The JavaScriptConverter, which you can add through RegisterConverters(...) seems to be made for Dictionary.

I would like to get

{"Currency":"USD","Amount":100.31}

after I serialize. Also, changing to double is out of the question. And I probably need to do some rounding (100.311 should become 100.31).

Does anyone know how to do this? Is there perhaps an alternative to the JavaScriptSerializer that lets you control the serializing in more detail?

like image 908
Halvard Avatar asked Sep 05 '12 13:09

Halvard


People also ask

Can you use decimals in JSON?

JSON does not have distinct types for integers and floating-point values. Therefore, the presence or absence of a decimal point is not enough to distinguish between integers and non-integers. For example, 1 and 1.0 are two ways to represent the same value in JSON.

What is serializing in JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).

How do you serialize decimal objects in Python?

To JSON serialize a Decimal object with Python, we can use the json. dumps method from the simplejson module with use_decimal set to True .

Can JSON serialize a list?

To serialize a collection - a generic list, array, dictionary, or your own custom collection - simply call the serializer with the object you want to get JSON for. Json.NET will serialize the collection and all of the values it contains.


3 Answers

I wasn't completely satisfied with all of the techniques thus far to achieve this. JsonConverterAttribute seemed the most promising, but I couldn't live with hard-coded parameters and proliferation of converter classes for every combination of options.

So, I submitted a PR that adds the ability to pass various arguments to JsonConverter and JsonProperty. It's been accepted upstream and I expect will be in the next release (whatever's next after 6.0.5)

You can then do it like this:

public class Measurements
{
    [JsonProperty(ItemConverterType = typeof(RoundingJsonConverter))]
    public List<double> Positions { get; set; }

    [JsonProperty(ItemConverterType = typeof(RoundingJsonConverter), ItemConverterParameters = new object[] { 0, MidpointRounding.ToEven })]
    public List<double> Loads { get; set; }

    [JsonConverter(typeof(RoundingJsonConverter), 4)]
    public double Gain { get; set; }
}

Refer to the CustomDoubleRounding() test for an example.

like image 117
BrandonLWhite Avatar answered Sep 29 '22 08:09

BrandonLWhite


For future reference, this can be achieved in Json.net pretty elegantly by creating a custom JsonConverter

public class DecimalFormatJsonConverter : JsonConverter
{
    private readonly int _numberOfDecimals;

    public DecimalFormatJsonConverter(int numberOfDecimals)
    {
        _numberOfDecimals = numberOfDecimals;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var d = (decimal) value;
        var rounded = Math.Round(d, _numberOfDecimals);
        writer.WriteValue((decimal)rounded);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    }

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

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(decimal);
    }
}

If you're creating serializers in code using constructor explicitly, this will work fine but I think it's nicer to decorate the relevant properties with JsonConverterAttribute, in which case the class must have a public, parameterless constructor. I solved this by creating a subclass which is specific to the format I want.

public class SomePropertyDecimalFormatConverter : DecimalFormatJsonConverter
{
    public SomePropertyDecimalFormatConverter() : base(3)
    {
    }
}

public class Poco 
{
    [JsonConverter(typeof(SomePropertyDecimalFormatConverter))]
    public decimal SomeProperty { get;set; }
}

The custom converter has been derived from Json.NET documentation.

like image 10
htuomola Avatar answered Sep 29 '22 08:09

htuomola


I just went through the same trouble as I had some decimals being serialized with 1.00 and some with 1.0000. This is my change:

Create a JsonTextWriter that can round the value to 4 decimals. Every decimal will then be rounded to 4 decimals: 1.0 becomes 1.0000 and 1.0000000 becomes also 1.0000

private class JsonTextWriterOptimized : JsonTextWriter
{
    public JsonTextWriterOptimized(TextWriter textWriter)
        : base(textWriter)
    {
    }
    public override void WriteValue(decimal value)
    {
        // we really really really want the value to be serialized as "0.0000" not "0.00" or "0.0000"!
        value = Math.Round(value, 4);
        // divide first to force the appearance of 4 decimals
        value = Math.Round((((value+0.00001M)/10000)*10000)-0.00001M, 4); 
        base.WriteValue(value);
    }
}

Use your own writer instead of the standard one:

var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create();
var sb = new StringBuilder(256);
var sw = new StringWriter(sb, CultureInfo.InvariantCulture);
using (var jsonWriter = new JsonTextWriterOptimized(sw))
{
    jsonWriter.Formatting = Formatting.None;
    jsonSerializer.Serialize(jsonWriter, instance);
}
like image 8
Corneliu Avatar answered Sep 29 '22 09:09

Corneliu