Right now I have an attribute called Checkbox. We're using it because of our front end posts "On" and "Off" instead of true/false when a checkbox value is submitted.
Our goal is to parse the on/off values and convert them to true/false before they get to the JSON converter, so they can be picked up as a boolean.
I've considered using this attribute to handle that.
[Checkbox]
[JsonConverter(typeof(InvariantConverter))]
public bool CheckboxInputValue { get; set; }
I need access to the value of the property inside of the checkbox attribute and then need the ability to modify the value.
Open to suggestions and thoughts here.
You cannot store per-instance data in an attribute property since the attribute is created when using the reflection API on the class.
You should rather use a custom JSON converter to convert between the string values and the boolean value.
As Martin Ullrich already suggested you should consider using a dedicated JSON converter.
I left null value handling for you.
public class OnOffStringToBoolConverter : JsonConverter
{
private readonly Type _sourceType = typeof(string);
private readonly Type _targetType = typeof(bool);
public OnOffStringToBoolConverter()
{
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override bool CanConvert(Type objectType)
{
if (objectType == null)
{
throw new ArgumentNullException(nameof(objectType));
}
return objectType == _sourceType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (objectType == null)
{
throw new ArgumentNullException(nameof(objectType));
}
if (serializer == null)
{
throw new ArgumentNullException(nameof(serializer));
}
if (reader.Value == null)
{
// Add some null handling logic here if needed.
throw new JsonSerializationException(
$"Unable to deserialize null value to {_targetType.Name}.");
}
if (string.Compare(reader.Value.ToString(), "On", StringComparison.OrdinalIgnoreCase) == 0)
{
return true;
}
if (string.Compare(reader.Value.ToString(), "Off", StringComparison.OrdinalIgnoreCase) == 0)
{
return false;
}
throw new JsonSerializationException(
$"Unable to deserialize '{reader.Value}' to {_targetType.FullName}. " +
$"This converter supports only \"On\", \"Off\" values.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (serializer == null)
{
throw new ArgumentNullException(nameof(serializer));
}
if (value == null)
{
// Add some null handling logic here if needed.
throw new JsonSerializationException("Unable to serialize null value.");
}
// Write value only if it is boolean type.
if (value is bool boolValue)
{
writer.WriteValue(boolValue ? "On" : "Off");
}
else
{
throw new JsonSerializationException(
$"Unable to serialize '{value}' of type {value.GetType().FullName}. " +
$"This converter supports deserialization of values " +
$"of {_targetType.FullName} type only.");
}
}
}
Related
Given this class:
public class Foo
{
public double? Bar { get; set; }
}
To serialize double.NaN as null, it works:
var foo = new Foo { Bar = double.NaN };
var test = JsonConvert.SerializeObject(foo, new JsonSerializerSettings { FloatFormatHandling = FloatFormatHandling.DefaultValue });
// {"Bar":null} // ok
But to deserialize NaN as null, it doesn't work:
var test2 = JsonConvert.SerializeObject(foo);
// {"Bar":"NaN"}
var test3 = JsonConvert.DeserializeObject<Foo>(test2, new JsonSerializerSettings { FloatFormatHandling = FloatFormatHandling.DefaultValue });
// test3.Bar = NaN // Not OK, I want it to be null
Is there any easier solution than creating his own custom converter ? https://stackoverflow.com/a/13801482/717058
I don't understand why you wouldn't want to use a converter, that would be the least obtrusive way to get what you want. Other solutions here basically pre-/post-processes the conversion which is an unnecessary step. Worst off, you're modifying the class in such a way that is impractical and very manual. Are you going to add an OnDeserialized() method to every type you want to ensure every floating point property has "real" values? That method should probably be reserved solely for general library development, not one-offs like this. That doesn't make it "easier."
With a converter, you do not have to modify the class at all, you could always specify at conversion and will handle all compatible conversions within the json object (since you say there are many other double properties).
JsonConvert.DeserializeObject<Foo>(test2, new NoNanRealConverter());
public class NoNanRealConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var type = Nullable.GetUnderlyingType(objectType) ?? objectType;
return new[] { typeof(float), typeof(double), typeof(decimal) }.Contains(type);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var nullableBase = Nullable.GetUnderlyingType(objectType);
var type = nullableBase ?? objectType;
if (nullableBase != null && reader.TokenType == JsonToken.Null)
return null;
if (type == typeof(double))
{
var value = Convert.ToDouble(reader.Value);
return Double.IsNaN(value) ? null : value;
}
else if (type == typeof(float))
{
var value = Convert.ToSingle(reader.Value);
return Single.IsNaN(value) ? null : value;
}
return Convert.ToDecimal(reader.Value);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
writer.WriteNull();
else if (value is double d && Double.IsNaN(d))
writer.WriteNull();
else if (value is float f && Single.IsNaN(f))
writer.WriteNull();
else
writer.WriteValue(value);
}
}
If you want even more control over the conversion process, you could make a custom contract resolver.
Try this:
public class Foo
{
public double? Bar { get; set; }
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (Bar == double.NaN)
Bar = null;
}
}
Reference: Serialization Attributes
As #Martin.Martinsson noticed "The behavior is correct and logical: deserialization of NaN will produce NaN", null will produce null. If you don't likeit for some reasons, the easiest way is to replace NaN with null using string functions
jsonString=jsonString.Replace(":NaN",":null");
or you can add on OnDeserialized method to class
public class Foo
{
.....
[OnDeserialized]
private void OnDeserializedBar(StreamingContext context)
{
if (Bar.ToString() == Double.NaN.ToString()) Bar=null;
}
}
I think you can even use both of them in the same time.
I have a CotractResolver that returns properties for Time part of each DateTime property (so that user can set time separately).
I have a TimeValueProvider that has a SetValue method as follows:
public void SetValue(object target, object value)
{
try
{
var time = value as string;
var originalValue = _propertyInfo.GetValue(target);
if (value == null)
{
_propertyInfo.SetValue(target, originalValue);
}
else if (string.IsNullOrWhiteSpace(time))
{
var originalDateTime = (DateTime?) originalValue ?? SqlDateTime.MinValue.Value;
_propertyInfo.SetValue(target,
new DateTime(originalDateTime.Year, originalDateTime.Month, originalDateTime.Day, 0, 0, 0));
}
else
{
var currentValue = GetCurrentValue(_propertyInfo.GetValue(target));
var convertedDate = TimeSpan.Parse(time, new DateTimeFormatInfo {LongTimePattern = "HH:mm:ss"});
var finalValue = new DateTime(currentValue.Year, currentValue.Month, currentValue.Day,
convertedDate.Hours, convertedDate.Days, convertedDate.Seconds);
_propertyInfo.SetValue(target, finalValue);
}
}
catch (InvalidDataException)
{
throw new ValidationException(new[]
{
new ValidationError
{
ErrorMessage = "Time is not correct",
FieldName = _propertyInfo.Name,
TypeName = _propertyInfo.DeclaringType.FullName
}
});
}
}
The problem is whenever I pass an invalid number as time say for example 99:99 an exception is thrown by TimeSpan.Parse but I am not getting it outside this method thus Json.Net deserializes the object.
I have chekced my code and couldn't find any general exception handling in place that causes such behavior.
Am I missing something about contract resolvers and value providers here ?
UPDATE: here's how I have configured Json.net :
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new EntityContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace;
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
Your problem is that you are trying to parse the JSON string inside IValueProvider.SetValue(). However, the value provider is only called after the JSON has been deserialized. Its purpose is to set the deserialized value inside the container object. Thus your current SetValue() method never actually does anything, because:
The incoming object value will be a DateTime not a string if deserialization was successful.
The method will not be called at all if the date string was invalid, because an exception will already have been thrown.
What you need to do instead is use a custom JsonConverter to parse the JSON date string and combine it with the existing value. JsonConverter.ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) has an argument existingValue that contains the current value of the property, so this is straightforward:
public class DateTimeConverter : JsonConverter
{
public override bool CanWrite { get { return false; } }
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var token = JToken.Load(reader);
if (token.Type == JTokenType.Date)
{
// Json.NET already parsed the date successfully. Return it.
return (DateTime)token;
}
else
{
TimeSpan span;
if (token.Type == JTokenType.TimeSpan)
{
// Not sure this is actually implemented, see
// http://stackoverflow.com/questions/13484540/how-to-parse-a-timespan-value-in-newtonsoft-json/13505910#13505910
span = (TimeSpan)token;
}
else
{
var timeString = (string)token;
if (String.IsNullOrWhiteSpace(timeString))
span = new TimeSpan();
else
{
try
{
span = TimeSpan.Parse(timeString, new DateTimeFormatInfo { LongTimePattern = "HH:mm:ss" });
}
catch (Exception ex)
{
throw new ValidationException(ex.Message);
}
}
}
var currentValue = (DateTime?)existingValue ?? SqlDateTime.MinValue.Value;
// Combine currentValue & TimeSpan and return. REPLACE THIS WITH YOUR OWN LOGIC.
// I don't really know how you want to do this.
return new DateTime(currentValue.Year, currentValue.Month, currentValue.Day) + span;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then apply it in your EntityContractResolver as follows:
public class EntityContractResolver : DefaultContractResolver
{
DateTimeConverter converter = null;
DateTimeConverter Converter
{
get
{
if (converter == null)
converter = Interlocked.CompareExchange(ref converter, new DateTimeConverter(), null);
return converter;
}
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
if (jProperty.PropertyType == typeof(DateTime) || jProperty.PropertyType == typeof(DateTime?))
{
jProperty.Converter = jProperty.MemberConverter = Converter;
}
return jProperty;
}
}
Sample fiddle.
I'm having trouble finding a way to automatically deserialize (server side) all EmptyOrWhiteSpace strings to null . Json.Net by default simply assigns the value to the object property, and I need to verify string by string whether it is empty or white space, and then set it to null.
I need this to be done upon deserialization, so I don't have to remember to verify every single string that comes from the client.
How can I override this on Json Net?
After a lot of source digging, I solved my problem.
Turns out all the solutions proposed in the comments only work if I am deserializing a complex object which contains a property that is a string.
In this case, yes, simply modifying the contract resolver works [1].
However, what I needed was a way to convert any string to null upon deserialization, and modifying the contract this way will fail for the case where my object is just a string, i.e.,
public void MyMethod(string jsonSomeInfo)
{
// At this point, jsonSomeInfo is "\"\"",
// an emmpty string.
var deserialized = new JsonSerializer().Deserialize(new StringReader(jsonSomeInfo), typeof(string));
// deserialized = "", event if I used the modified contract resolver [1].
}
What happens is that when we work with a complex object, internally JSON.NET assigns a TokenType of JsonToken.StartObject to the reader, which will cause the deserialization to follow a certain path where property.ValueProvider.SetValue(target, value); is called.
However, if the object is just a string, the TokenType will be JsonToken.String, and the path will be different, and the value provider will never be invoked.
In any event, my solution was to build a custom converter to convert JsonReaders that have TokenType == JsonToken.String (code below).
Solution
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null) return null;
string text = reader.Value.ToString();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
return text;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not needed because this converter cannot write json");
}
public override bool CanWrite
{
get { return false; }
}
}
[1] Credits to #Raphaƫl Althaus.
public class NullToEmptyStringResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return type.GetProperties()
.Select(p => {
var jp = base.CreateProperty(p, memberSerialization);
jp.ValueProvider = new EmptyToNullStringValueProvider(p);
return jp;
}).ToList();
}
}
public class EmptyToNullStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
public EmptyToNullStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
}
public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result != null && string.IsNullOrWhiteSpace(result.ToString()))
{
result = null;
}
return result;
}
public void SetValue(object target, object value)
{
if (_MemberInfo.PropertyType == typeof(string) && value != null && string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}
_MemberInfo.SetValue(target, value);
}
}
I am trying to perform custom serialisation, all the happy path code works but the null value path is not behaving as I'd like.
I have set the serializer settings to NullValueHandling.Ignore and other parts of my object graph that are null (and don't use my custom serialization) have the null values removed. It looks like the Newtonsoft serializer writes to a string builder so we should be able to 'rewind' any written json tokens but I don't see how to not write anything.
Doing nothing and just returning causes the serializer to throw an exception as the json would be invalid.
Any clues?
public class SpecialConvertor : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null || (int)value == 0)
{
if (serializer.NullValueHandling == NullValueHandling.Ignore)
{
//how to make this work?
}
else
{
writer.WriteNull();
}
return;
}
// the rest of WriteJson
}
// the rest of SpecialConvertor
}
NullValueHandling is for object references. In your example, your value is an integer. To omit integer properties with default values, use the setting DefaultValueHandling = DefaultValueHandling.Ignore.
The null check in WriteJson() should not be necessary because Json.NET never calls the converter with a null value. Instead, it writes the name & null value itself -- or not, if NullValueHandling == NullValueHandling.Ignore. So checking for null and rewinding should never be required.
A null value for an object property might still get written when null value handling or default value handling are Ignore if one of your converters writes it explicitly in WriteJson. To prevent that, you can check the settings and skip nulls like so:
public class MyClassConverter : JsonConverter
{
const string Prefix = "My Value Is: ";
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var myClass = (MyClass)value;
writer.WriteStartObject();
if (myClass.StringValue != null
|| (serializer.NullValueHandling != NullValueHandling.Ignore
&& (serializer.DefaultValueHandling & DefaultValueHandling.Ignore) != DefaultValueHandling.Ignore))
{
writer.WritePropertyName("StringValue");
if (myClass.StringValue == null)
writer.WriteNull();
else
serializer.Serialize(writer, Prefix + myClass.StringValue);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JValue.Load(reader);
if (s.StartsWith(Prefix))
s = s.Substring(Prefix.Length);
return s;
}
public override bool CanConvert(Type objectType) { return objectType == typeof(MyClass); }
}
[JsonConverter(typeof(MyClassConverter))]
public class MyClass
{
public string StringValue { get; set; }
}
I have a web api application using asp.net mvc web api that recieve some decimal numbers in viewmodels. I would like to create a custom model binder for decimal type and get it working for all decimals numbers. I have a viewModel like this:
public class ViewModel
{
public decimal Factor { get; set; }
// other properties
}
And the front-end application can send a json with a invalid decimal number like: 457945789654987654897654987.79746579651326549876541326879854
I would like to response with a 400 - Bad Request error and a custom message. I tried create a custom model binder implementing the System.Web.Http.ModelBinding.IModelBinder and registring on the global.asax but does not work. I would like to get it working for all decimals in my code, look what I tried:
public class DecimalValidatorModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var input = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (input != null && !string.IsNullOrEmpty(input.AttemptedValue))
{
if (bindingContext.ModelType == typeof(decimal))
{
decimal result;
if (!decimal.TryParse(input.AttemptedValue, NumberStyles.Number, Thread.CurrentThread.CurrentCulture, out result))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, ErrorHelper.GetInternalErrorList("Invalid decimal number"));
return false;
}
}
}
return true; //base.BindModel(controllerContext, bindingContext);
}
}
Adding on the Application_Start:
GlobalConfiguration.Configuration.BindParameter(typeof(decimal), new DecimalValidatorModelBinder());
What can I do?
Thank you.
By default Web API reads a complex type from the request body using a media-type formatter. So it doesn't go through a model binder in this case.
for JSON you can create JsonConverter (in case you are sticking by default with JSON.NET:
public class DoubleConverter : JsonConverter
{
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(double) || objectType == typeof(double?));
}
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<double>();
}
if (token.Type == JTokenType.String)
{
// customize this to suit your needs
var wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
var alternateSeparator = wantedSeperator == "," ? "." : ",";
double actualValue;
if (double.TryParse(token.ToString().Replace(alternateSeparator, wantedSeperator), NumberStyles.Any,
CultureInfo.CurrentCulture, out actualValue))
{
return actualValue;
}
else
{
throw new JsonSerializationException("Unexpected token value: " + token.ToString());
}
}
if (token.Type == JTokenType.Null && objectType == typeof(double?))
{
return null;
}
if (token.Type == JTokenType.Boolean)
{
return token.ToObject<bool>() ? 1 : 0;
}
throw new JsonSerializationException("Unexpected token type: " + token.Type.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanWrite is false. The type will skip the converter.");
}
}