Handling decimal values in Newtonsoft.Json
You can handle both formats (the JSON number representation and the masked string format) using a custom JsonConverter
class like this.
class DecimalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(decimal) || objectType == typeof(decimal?));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer)
{
return token.ToObject<decimal>();
}
if (token.Type == JTokenType.String)
{
// customize this to suit your needs
return Decimal.Parse(token.ToString(),
System.Globalization.CultureInfo.GetCultureInfo("es-ES"));
}
if (token.Type == JTokenType.Null && objectType == typeof(decimal?))
{
return null;
}
throw new JsonSerializationException("Unexpected token type: " +
token.Type.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To plug this into your binder, just add an instance of the converter to the Converters
list in the JsonSerializerSettings
object:
JsonSerializerSettings settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
Formatting = Formatting.None,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
Converters = new List<JsonConverter> { new DecimalConverter() }
};
Handling invalid inputs when deserializing JSON to decimal values
You can solve this problem by making a custom JsonConverter
to handle the decimals:
class TolerantDecimalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Integer)
{
return Convert.ToDecimal(reader.Value);
}
if (reader.TokenType == JsonToken.String && decimal.TryParse((string)reader.Value, out decimal d))
{
return d;
}
return 0.0m;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, just add an instance to the Converters
collection in the JsonSerializerSettings
that you are passing to JsonConvert.DeserializeObject<T>
.
Settings.Converters.Add(new TolerantDecimalConverter());
Note: since you are using decimals, you should probably also set FloatParseHandling
to Decimal
if you are not already; the default is Double
.
Settings.FloatParseHandling = FloatParseHandling.Decimal;
Working demo here: https://dotnetfiddle.net/I4n00o
deserialize number with comma in json to decimal
It's about current thread culture.
en-US
separator is .
PriceModel value = JsonConvert.DeserializeObject<PriceModel>("{'Price': '1234,99'}", new JsonSerializerSettings
{
// tr culture separator is ","..
Culture = new System.Globalization.CultureInfo("tr-TR") //Replace tr-TR by your own culture
});
and check this.
https://msdn.microsoft.com/en-us/en-en/library/3ebe5aks(v=vs.110).aspx?f=255&MSPPError=-2147217396
NewtonSoft Json deserialize decimal numbers with more then 8 decimals
I figured out the solution myself.
First I created the convertor below
public Class JsonGeometryConverter
Inherits JsonConverter
Public Overrides Function CanConvert(objectType As Type) As Boolean
Return True
End Function
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Dim valF As double = reader.Value
Dim valFS = String.Format("{0:G17}", valF)
Return valFS
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Throw New NotImplementedException()
End Sub
End Class
Then I apply the converter attribute to the properties to extract values to
Public Class RestGIS_LocationInfo
<JsonConverter(GetType(JsonGeometryConverter))>
Public Property X As string = ""
<JsonConverter(GetType(JsonGeometryConverter))>
Public Property Y As String = ""
End Class
The converter reads the actual value from json data as double, and returns it formatted with up to 17 digits of precision (see https://msdn.microsoft.com/en-us/library/kfsatb94.aspx for details)
Json.Net not serializing decimals the same way twice
If your polymorphic models contain decimal
properties, in order not to lose precision, you must temporarily set JsonReader.FloatParseHandling
to be FloatParseHandling.Decimal
when pre-loading your JSON into a JToken
hierarchy, like so:
public class TestItemJsonConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = null;
var old = reader.FloatParseHandling;
try
{
reader.FloatParseHandling = FloatParseHandling.Decimal;
JObject jObj = JObject.Load(reader);
string itemTypeID = jObj["ItemName"].Value<string>();
//NOTE: My real implementation doesn't have hard coded strings or types here.
//See the code block below for actual implementation.
if (itemTypeID == "Item1")
result = jObj.ToObject(typeof(Item1), serializer);
}
finally
{
reader.FloatParseHandling = old;
}
return result;
}
Demo fiddle here.
Why is this necessary? As it turns out, you have encountered an unfortunate design decision in Json.NET. When JsonTextReader
encounters a floating-point value, it parses it to either decimal
or double
as defined by the above-mentioned FloatParseHandling
setting. Once the choice is made, the JSON value is parsed into the target type and stored in JsonReader.Value
, and the underlying character sequence is discarded. Thus, if a poor choice of floating-point type is made, it's difficult to correct the mistake later on.
So, ideally we would like to choose as a default floating-point type the "most general" floating point type, one that can be converted to all others without loss of information. Unfortunately, in .Net no such type exists. The possibilities are summarized in Characteristics of the floating-point types:
As you can see, double
supports a larger range while decimal
supports a larger precision. As such, to minimize data loss, sometimes decimal
would need to be chosen, and sometimes double
. And, again unfortunately, no such logic is built into JsonReader
; there is no FloatParseHandling.Auto
option to choose the most appropriate representation.
In the absence of such an option or the ability to load the original floating-point value as a string and re-parse it later, you will need to hardcode your converter with an appropriate FloatParseHandling
setting based upon your data model(s) when you pre-load your JToken
hierarchy.
In cases where your data models contain both double
and decimal
members, pre-loading using FloatParseHandling.Decimal
will likely meet your needs, because Json.NET will throw a JsonReaderException
when attempting to deserialize a too-large value into a decimal
(demo fiddle here) but will silently round the value off when attempting to deserialize a too-precise value into a double
. Practically speaking, it's unlikely you will have floating-point values larger than 10^28
with more than 15 digits of precision + trailing zeros in the same polymorphic data model. In the unlikely chance you do, by using FloatParseHandling.Decimal
you'll get an explicit exception explaining the problem.
Notes:
I don't know why
double
was chosen instead ofdecimal
as the "default default" floating point format. Json.NET was originally released in 2006; my recollection is thatdecimal
wasn't widely used back then, so maybe this is a legacy choice that was never revisited?When deserializing directly to a
decimal
ordouble
member, the serializer will override the default floating-point type by callingReadAsDouble()
orReadAsDecimal()
, so precision is not lost when deserializing directly from a JSON string. The problem only arises when pre-loading into aJToken
hierarchy then subsequently deserializing.Utf8JsonReader
andJsonElement
from system.text.json, Microsoft's replacement for Json.NET in .NET Core 3.0, avoid this problem by always maintaining the underlying byte sequence of a floating-point JSON value, which is one example of the new API being an improvement on the old.If you actually have values larger than
10^28
with more than 15 digits of precision + trailing zeros in the same polymorphic data model, switching to this new serializer might be a valid option.
Custom rule for deserializing decimal values in JSON.NET
You can either use annotations in the type you're deserializing, or specify custom converters/settings when deserializing (instead of globally). The only good way of only handling some decimal
properties is to use annotations, I think.
string json = @"{""val"": null}";
public class NoAnnotation {
public decimal val {get; set;}
}
public class WithAnnotation {
[JsonConverter(typeof(CustomDecimalNullConverter))]
public decimal val {get; set;}
}
void Main()
{
// Converting a type that specifies the converter
// with attributes works without additional setup
JsonConvert.DeserializeObject(json, typeof(WithAnnotation));
// Converting a POCO doesn't work without some sort of setup,
// this would throw
// JsonConvert.DeserializeObject(json, typeof(NoAnnotation));
// You can specify which extra converters
// to use for this specific operation.
// Here, the converter will be used
// for all decimal properties
JsonConvert.DeserializeObject(json, typeof(NoAnnotation),
new CustomDecimalNullConverter());
// You can also create custom serializer settings.
// This is a good idea if you need to serialize/deserialize multiple places in your application,
// now you only have one place to configure additional converters
var settings = new JsonSerializerSettings();
settings.Converters.Add(new CustomDecimalNullConverter());
JsonConvert.DeserializeObject(json, typeof(NoAnnotation), settings);
}
// For completeness: A stupid example converter
class CustomDecimalNullConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return 0m;
}
else
{
return Convert.ToDecimal(reader.Value);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue((decimal)value);
}
}
Preserving trailing zeros when selecting values from Json String using Newtonsoft
Because C# decimal types preserve trailing zeros, you can just instruct Json.Net to parse numbers to decimals instead of floating points. Do this by setting FloatParseHandling
to FloatParseHandling.Decimal
as shown here:
var json = @"{ ""obj1"": [{ ""n1"": ""n"", ""n2"": 1.000, ""n3"": true }, { ""n1"": ""n"", ""n2"": 10.000, ""n3"": false }] }";
var token = JsonConvert.DeserializeObject<JToken>(
json,
new JsonSerializerSettings { FloatParseHandling = FloatParseHandling.Decimal });
token["obj1"][0]["n2"]
will now show 1.000
instead of 1
.
Related Topics
Is There a Faster Way to Scan Through a Directory Recursively in .Net
Better Naming in Tuple Classes Than "Item1", "Item2"
Webbrowser Control IE8 Compatibility Mode On/Off Switch
How to Indefinitely Pause a Thread
Using C# Reflection to Call a Constructor
C# - Winforms - Global Variables
Enforce an Async Method to Be Called Once
What's the Hardest or Most Misunderstood Aspect of Linq
Windows Service to Run Constantly
Opening a Folder in Explorer and Selecting a File
Custom Numeric Format String to Always Display the Sign
Docking Window Inside Another Window
Why Can't Datetime.Parse Parse Utc Date
How to Test for the Presence of an Action Filter with Constructor Arguments