Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.NET serializing float/double with minimal decimal places, i.e. no redundant ".0"?

When serializing floats and doubles, Json.NET always adds ".0" at the end if the number doesn't contain any fractional part. I was wondering if there was an easy way to bypass this, to result in a more compact representation? The extra periods and zeroes add up when serializing an object containing many numbers.

For example, when running this code:

JsonConvert.SerializeObject(1.0); 

I would expect (and want) this result:

"1" 

But instead I get:

"1.0" 

I looked at the source code and noticed that it was purposely added in commit 0319263 ("...-Fixed JsonConvert to always write a floating point number with a decimal place...") where it runs code that looks basically like:

    private static string EnsureDecimalPlace(double value, string text)     {         if (double.IsNaN(value) || double.IsInfinity(value) ||             text.IndexOf('.') != -1 || text.IndexOf('E') != -1 ||             text.IndexOf('e') != -1)         {             return text;         }          return text + ".0";     } 

Consequently, I am wondering:

  1. What may have been the reason for that change? The JSON specification does not seem to require it.

  2. Is there an easy way to bypass it?

like image 438
Arieh Avatar asked Jan 16 '14 04:01

Arieh


1 Answers

As an alternative answer to question 2 (assuming you don't want to go through the hassle of compiling your own custom version of the Json.NET source) you can create your own custom JsonConverter class to handle decimal, float, and double values. Here's the version I'm using:

class DecimalJsonConverter : JsonConverter {     public DecimalJsonConverter()     {     }      public override bool CanRead     {         get         {             return false;         }     }      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 CanConvert(Type objectType)     {         return (objectType == typeof(decimal) || objectType == typeof(float) || objectType == typeof(double));     }      public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)     {         if (DecimalJsonConverter.IsWholeValue(value))         {             writer.WriteRawValue(JsonConvert.ToString(Convert.ToInt64(value)));         }         else         {             writer.WriteRawValue(JsonConvert.ToString(value));         }     }      private static bool IsWholeValue(object value)     {         if (value is decimal)         {             decimal decimalValue = (decimal)value;             int precision = (Decimal.GetBits(decimalValue)[3] >> 16) & 0x000000FF;             return precision == 0;         }         else if (value is float || value is double)         {             double doubleValue = (double)value;             return doubleValue == Math.Truncate(doubleValue);         }          return false;     } } 

This will preserve the precision on values of type decimal. If you would prefer to ignore the precision of decimal values, you can make the decimal portion of the IsWholeValue() function work the same as the float/double portion:

    private static bool IsWholeValue(object value)     {         if (value is decimal)         {             decimal decimalValue = (decimal)value;             return decimalValue == Math.Truncate(decimalValue);         }         else if (value is float || value is double)         {             double doubleValue = (double)value;             return doubleValue == Math.Truncate(doubleValue);         }          return false;     } 

In either case, to use the above code, just call the serializer like this:

string json = JsonConvert.SerializeObject(value, new DecimalJsonConverter()) 
like image 102
jkindwall Avatar answered Sep 17 '22 17:09

jkindwall