Wednesday, June 5, 2024
Homepage · c#
 Popular · Latest · Hot · Upcoming
114
rated 0 times [  116] [ 2]  / answers: 1 / hits: 84180  / 10 Years ago, thu, june 5, 2014, 12:00:00

Edit: It's been almost 5 years and I don't think this is the way to go. The client should post the data in the correct numerical format. With current frameworks like React or Angular, or with a proper architecture and error handling & validation, i think this is almost a non-problem.



But if anyone wishes to flex their Json.NET muscles, feel free to check the answers.






I have a MVC application and I handle some JSON in it. That's 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 it works, since 9888.77 is a Javascript float value. I think.



But I have a masked input for money in my page that makes the JSON look like this (sorry about my english):



{MyProp: 9.888,77 }


AAAND, it fails. It says that it Could not convert string to decimal.



Ok, that's fair. It is not a JS float, but Convert.ToDecimal(9.888,77) works the way I want.



I've read some tutorials on the internet about custom deserializers, but its inviable for me to define a custom deserializer for every single class I have in my application.



What I want is to simple redefine the way JSON.Net converts a string to a decimal property, in any class i'll ever want to deserialize to. I want to inject the Convert.ToDecimal function in the process of converting decimals, when the current converter doesn't work.



Is there a way I could do it?



I thought there was a way to do it, so I changed my code a little 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 it is very ugly: it executes what I want only when it crashes, and depends on base.ReadAsDecimal() crashing. It couldn't be more ugly.



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



The value itself is being converted, but perhaps for some reason it still tries to put the string 1.231,23 into a decimal.



So, is there a way to do it properly?


More From » c#

 Answers
133

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() }
};

[#70716] Tuesday, June 3, 2014, 10 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
larrycodys

Total Points: 394
Total Questions: 93
Total Answers: 78

Location: Romania
Member since Mon, Jun 6, 2022
2 Years ago
;