I have a JSON file with an array of objects, each containing a string value, grade, that I'd like to parse to decimal.
The string value contains a valid decimal about 99% percent of the time, but in that 1%, I'm getting values such as "grade":"<1" which is obviously not a valid decimal. The grade property is about 1 of 100 properties that can sometimes be set to "<1".
This of course throws the following error:
Newtonsoft.Json.JsonReaderException: 'Could not convert string to
decimal'
Here is how I am currently deserializing my JSON:
public static Product FromJson(string json) => JsonConvert.DeserializeObject<Product>(json, Converter.Settings);
Is there anything I can do to handle cases where I'm getting those pesky "<1" values? Hopefully something that does the following: if attempting to deserialize a value to decimal, and if the value cannot be parsed to decimal, default to zero.
Any ideas if this is possible? I obviously don't want to have to update my table columns to switch all values from decimal to varchar, because that just sucks and is going to require decimal <-> varchar conversions every time someone wants to query my data.
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
Related
I'm having some difficulties in deserializing a Boolean from a json. I need that the value can be case insensitive (faLSe, tRUE, etc) and if it's an invalid value (ex: qwerty) I'll return a null. I've created a custom converter:
public class NullableBooleanJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string value = reader?.Value?.ToString();
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (bool.TryParse(value, out bool deserializedValue))
{
return deserializedValue;
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}
And I'm using the code like this:
JsonSerializerSettings setting = new JsonSerializerSettings
{ Converters = new List<JsonConverter> { this.converter } };
JsonConvert.DeserializeObject<bool?>("false", setting).Should().BeFalse();
and this works, but if I have on the last line:
JsonConvert.DeserializeObject<bool?>("faLSE", setting).Should().BeFalse();
The test fails with the message:
TestName threw exception:
Newtonsoft.Json.JsonReaderException: Error parsing boolean value. Path '', line 1, position 2
What I'm doing wrong?
If you look at the source code of DeserializeObject it uses the JsonTextReader class to parse the Json. If you look at the source code of JsonTextReader it parses only "true" (if first char is a lowercase t) or "false" (if first char a lowercase f) strings (row 1720), throwing an exception in any other case.
The simplest way to realize what you need is to download the full Newtonsoft.Json source code, add a String.ToLower to ParseTrue and ParseFalse methods in JsonTextReader.cs, recompile and use the new dll in your project.
I have a machine-generated class MyData which has sbyte members. The actual class is long and not very readable, but here is a fragment of it:
class MyData
{
private sbyte _MuxControl;
public sbyte MuxControl
{
get { return _MuxControl; }
set { __isset.MuxControl = true; this._MuxControl = value; }
}
}
The corresponding simplified JSON looks like this:
[
{
"MuxControl": 0xAA
}
]
I am attempting to deserialize like this:
var deserialized = JsonConvert.DeserializeObject<List<MyData>>(JsonStr);
Some values exceed sbyte range, for example 0xAA. As a result, exceptions are thrown. When I change the value to 0x1, for example, it works.
I can not touch the code of MyData. It's machine-generated. Is there a conversion setting, override or some other way to get these values to deserialize properly into sbyte?
You are getting the exception because the conversion methods Json.Net is using under the covers for sbyte are range checked, but what you really need here is an unchecked conversion (or more precisely, a bigger range). You can handle that with a custom JsonConverter like so:
public class SByteConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(sbyte);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Integer)
{
// Integer values come in as longs from the reader.
long val = (long)reader.Value;
// If the value fits in 8 bits, convert it to a signed byte.
if (val >= -128 && val <= 255)
{
return unchecked((sbyte)val);
}
// We got a value that can't fit in an sbyte.
throw new JsonSerializationException("Value was out of range for an sbyte: " + val);
}
// We got something we didn't expect, like a string or object.
throw new JsonSerializationException("Unexpected token type: " + reader.TokenType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Write sbyte values out in the same format we read them.
// Note this is technically invalid JSON per the spec.
writer.WriteRawValue("0x" + ((sbyte)value).ToString("X2"));
}
}
To use the converter, pass an instance of it to JsonConvert.DeserializeObject like this:
var deserialized = JsonConvert.DeserializeObject<List<MyData>>(JsonStr, new SByteConverter());
Working demo: https://dotnetfiddle.net/fEW6wy
I have a controller that returns a json in a function GET. I want to ignore\delete the empty, null value and the guide empty of my json, that I sent to a third part.
In the WebApiConfig I use:
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
And it works for the null value, but not to the empty\guid.empty value.
So I try to add config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new EmptyToNullConverter()); and the class EmptyToNullConverter. To convert the empty value in null and then removed by the other command but it doesn't work because it doesn't convert the values.
the class EmptyToNullConverter is:
public class EmptyToNullConverter : JsonConverter
{
private JsonSerializer _stringSerializer = new JsonSerializer();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string value = _stringSerializer.Deserialize<string>(reader);
if (string.IsNullOrEmpty(value) || value == Guid.Empty.ToString())
{
value = null;
}
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_stringSerializer.Serialize(writer, value);
}
}
How can I do?
Thanks a lot
Even when you are serializing if the object type is Guid. It still can't be null, trying to override at a serialization level could create confusion anyway as if the receiver is expecting a Guid in a response model. Even if you send nothing in a JSON response, their code may still initialise an empty Guid.
Is there any reason you couldn't use a nullable Guid? Given you are new to stack overflow i'm going to perhaps rudely assume your new to coding and so you might not be familiar with nullable value types.
Value types such as int, float, GUID, DateTime etc... will always have a default value, however you can force them to be nullable by declaring them with a question mark suffixed.
e.g.
int x // == 0;
int? x // == null;
Guid g // == 00000000-0000-0000-0000-000000000000;
Guid? g // == null;
A good general rule is that if an value type can be essentially null, then make it nullable. Then you won't need any fancy conversions.
I'm using a JObject to handle my client post's.
I convert the JObject into a strong type entity using the ToObject function.
When the datetime value isn't valid - let's say 29\05\2014(since there aren't 29 months), I get an exception:
Could not convert string to DateTime: 29/05/2014. Path 'PurchaseDate.Value'.
I understand the exception and I would like to prevent crashes in those kind of situations.
How can I tell the JObject to ignore invalid date values? In my specific case my entity is a nullable datetime object so I would like to keep in null if the parsing fails(rather then crash).
In this specific case I'm talking about a datetime, but if someone can give me a more generic answer on how I can prevent failures on "invalid parsing\conversions" that would be great, since all of my entities contain nullable fields and I don't want to handle validations on the client side.
You cannot disable them just for invalid dates, but you can stop the parsing of date values, storing them as strings and implement a custom parsing later.
jObject.ToObject<MyObject>( new JsonSerializer {
DateParseHandling = DateParseHandling.None
});
I found a work around - Adding a converter:
var js = new JsonSerializer
{
DateParseHandling = DateParseHandling.DateTime,
};
js.Converters.Add(new DateTimeConverter());
dynamic jsonObject = new JObject();
jsonObject.Date = "29/05/2014";
var entty = ((JObject)jsonObject).ToObject<Entity>(js);
Definitions:
public class Entity
{
public DateTime? Date { get; set; }
}
public class DateTimeConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DateTime val;
if (reader.Value != null && DateTime.TryParse(reader.Value.ToString(), out val))
{
return val;
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((DateTime)value).ToString("MM/dd/yyyy"));
}
}
I notice there are some other results on stackoverflow for this question but they don't seem to work or are vague. Using the most popular result, I have put together this:
The problem is that when JSON comes back and is being serialised into one of my custom types, one of the bits of JSON is sometimes an array, and sometimes just a string. If my custom type has a string, and the JSON is an array, I get an error. The same happens the other way around, if the JSON is an object and my custom type is an array. It just can't map it to the property.
I decided to solve this, I want to override the deserialisation of this particular property, and if it's an object, I want to convert it into an array of 1 object.
In the object I am serialising to, I added a JsonConverter which I think is going to override the way it's deserialised.
[JsonConverter(typeof(ArrayOrSingleObjectConverter<string>))]
public List<string> person { get; set; }
The idea is that the custom converter will convert a single object to an array. So if the JSON is "Hello" it will set person to be a List containing "Hello" instead of throwing an exception saying cannot convert string to List.
If it's already an array, it should just leave it alone.
The converter looks like this:
public class ArrayOrSingleObjectConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true; // Not sure about this but technically it can accept an array or an object, so everything is game.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (objectType == typeof(List<T>))
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
var singleObject = serializer.Deserialize<T>(reader);
var objectAsList = new List<T>();
objectAsList.Add(singleObject);
return objectAsList;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It doesn't work. The above code throws an exception trying to deserialize a a single string saying it can't cast it into a List inside the if statement (the 'objectype' is however a List).
I'm struggling to understand exactly what the read and write methods are doing. In the other answer on stackoverflow, it suggests throwing a NotImplementedException in the read method. But if I do that, the read method is called and the exception throws.
I think I'm on the right track, but I need a nudge in the right direction. I think I'm a little confused about what the ReadJSon method is doing and what its parameters mean.
I don't really understand where the value is coming from that it's deserializing since I didn't specify it in the Deserialize method call.
I'm a bit out of my depth on this one.
I had to do something similar last week and I came up with the following, which works fine for a List rather than an array
internal class GenericListCreationJsonConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
else
{
T t = serializer.Deserialize<T>(reader);
return new List<T>(new[] { t });
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
I like this method which makes Json.NET do all the heavy lifting. And as a result, it supports anything that Json.NET supports (List<>, ArrayList, strongly-typed arrays, etc).
public class FlexibleCollectionConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, objectType);
}
var array = new JArray(JToken.ReadFrom(reader));
return array.ToObject(objectType);
}
public override bool CanConvert(Type objectType)
{
return typeof (IEnumerable).IsAssignableFrom(objectType);
}
}