Decimal value handling in Newtonsoft.Json

Edit: Nearly 5 years have passed and I don't think this is the way to go. The client must publish the data in the correct numerical format. With current environments such as React or Angular, or with the right architecture, handling, and error checking, I think this is almost no problem.

But if someone wants to tighten the muscles of Json.NET, feel free to check the answers.


I have an MVC application and I am processing some JSON in it. So simple. I have this simple piece of code in my ModelBinder:

return JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, Formatting = Formatting.None, DateFormatHandling = DateFormatHandling.IsoDateFormat, FloatParseHandling = FloatParseHandling.Decimal }); 

And it works flawlessly.

Well, sort of.

Let's say I have this class:

 public class MyClass { public decimal MyProp { get; set; } } 

If I try to deserialize this JSON:

 "{\"MyProp\": 9888.77}" 

Of course, this works, since 9888.77 is a Javascript floating point value. I think.

But on my page there is a disguised input of money that makes JSON like this (sorry for my English):

 "{\"MyProp\": \"9.888,77\" }" 

AAAND, this fails. He says that Could not convert string to decimal .

Well, that’s fair. This is not a JS float, but Convert.ToDecimal("9.888,77") works the way I want.

I read some tutorials about custom deserializers on the Internet, but I cannot define my own deserializer for each class that is in my application.

I just want to override the way JSON.Net converts a string to a decimal property, in any class that I ever want to deserialize to. I want the Convert.ToDecimal function Convert.ToDecimal to process decimal fractions when the current converter is down.

Is there any way I could do this?

I thought there was a way to do this, so I changed my code a bit.

 JsonSerializer serializer = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, Formatting = Formatting.None, DateFormatHandling = DateFormatHandling.IsoDateFormat, FloatParseHandling = FloatParseHandling.Decimal, }; return serializer.Deserialize(new DecimalReader(jsonStr), bindingContext.ModelType); 

And created this class:

 public class DecimalReader : JsonTextReader { public DecimalReader(string s) : base(new StringReader(s)) { } public override decimal? ReadAsDecimal() { try { return base.ReadAsDecimal(); } catch (Exception) { if (this.TokenType == JsonToken.String) { decimal value = 0; bool convertible = Decimal.TryParse(this.Value.ToString(), out value); if (convertible) { return new Nullable<decimal>(value); } else { throw; } } else { throw; } } } } 

But this is very ugly: it does what I want only when it crashes, and depends on base.ReadAsDecimal() . It could not have been more ugly.

And it does not work : Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y. Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y. Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y. Error converting value "1.231,23" to type 'System.Nullable1[System.Decimal]'. Path 'MyProp', line X, position Y.

The value itself is converted, but perhaps for some reason it is still trying to put the string "1.231.23" in decimal.

So, is there a way to do this right?

+9
source share
2 answers

You can handle both formats (JSON number representation and masked string format) using a native 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 connect this to your binder, simply add the converter instance 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() } }; 
+23
source

Thanx a lot! I was looking for a solution to do decimals, which are always serialized in the same way, and this post sent me in the right direction. This is my code:

  internal 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) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { Decimal? d = default(Decimal?); if (value != null) { d = value as Decimal?; if (d.HasValue) // If value was a decimal?, then this is possible { d = new Decimal?(new Decimal(Decimal.ToDouble(d.Value))); // The ToDouble-conversion removes all unnessecary precision } } JToken.FromObject(d).WriteTo(writer); } } 
+2
source

Source: https://habr.com/ru/post/970360/


All Articles