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"));
}
}
Related
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
I have an input date string in ISO 8601 format (2020-07-23T09:42:02.694Z). And in my Controller the Property is defined as DateTime. I have written a custom date converter to convert the date to LocalTime zone (this is unusual).
public class UTCtoUnspecifiedDateConverter : IsoDateTimeConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader != null
&& reader.Value != null
&& this.CanConvert(objectType)
&& this.CanRead
&& typeof(DateTime) == reader.Value.GetType())
{
return DateTime.SpecifyKind((DateTime)reader.Value, DateTimeKind.Unspecified).ToLocalTime();
}
else
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
}
Is there any way to get the raw string that I am sending from the Client in this converter?
Basically I want to check if there is a Z at the end or not.
While debugging I saw a private property on the reader which stores this information. Any way to get this here?
If you are using a custom JsonConverter to handle dates, you need to set the DatePraseHandling setting to None.
var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.Converters.Add(new UTCtoUnspecifiedDateConverter());
jsonSettings.DateParseHandling = DateParseHandling.None;
Then, in your converter, you will get date values from the reader as strings which you can parse using DateTime.TryParse or DateTime.TryParseExact.
As an aside, there are some checks you are making inside of ReadJson that are not necessary. You shouldn't need to call CanConvert or CanRead, because Json.Net does that for you in order to determine whether to call ReadJson in the first place. Also, the reader passed to ReadJson will never be null, so you don't need to check for that either. So that simplifies the code quite a bit.
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value is string &&
DateTime.TryParseExact((string)reader.Value, DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date))
{
return DateTime.SpecifyKind(date, DateTimeKind.Unspecified).ToLocalTime();
}
else
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
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 want to deserialize JSON containing long decimal values into custom types to maintain their precision (i.e., a custom BigDecimal class). I'm using Json.NET 9.0.1 and .NET 4.6.1. I've tried using a JsonConverter, but it seems that the value available when ReadJson is called has already been identified and read by Json.NET as a .NET decimal type and is limited to its precision.
Ideally I would have access to the raw string so I could put it in a custom type. I can use string properties on the target object and it deserializes the full string, but then I'd have to further process the object (i.e., copy it into another representation) and that's especially messy across a large schema.
Any thoughts on a better approach?
Here's the target class:
public class DecimalTest
{
public string stringValue { get; set; }
public decimal decimalValue { get; set; }
public BigDecimal bigDecimalValue { get; set; }
}
Here's a test with JSON:
[TestMethod]
public void ReadBigDecimal_Test()
{
var json = #"{
""stringValue"" : 0.0050000012852251529693603515625,
""decimalValue"" : 0.0050000012852251529693603515625,
""bigDecimalValue"" : 0.0050000012852251529693603515625
}";
var settings = new JsonSerializerSettings();
settings.FloatParseHandling = FloatParseHandling.Decimal;
settings.Converters.Add(new JsonBigDecimalConverter());
var result = JsonConvert.DeserializeObject<DecimalTest>(json, settings);
Assert.IsNotNull(result);
Assert.AreEqual("0.0050000012852251529693603515625", result.stringValue);
Assert.AreEqual(0.0050000012852251529693603516m, result.decimalValue);
// *** This case fails ***
Assert.AreEqual("0.0050000012852251529693603515625", result.bigDecimalValue.ToString());
}
Here's the custom converter:
public class JsonBigDecimalConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BigDecimal));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// *** reader.Value here already appears to be a .NET decimal.
// *** If I had access to the raw string I could get this to work.
return BigDecimal.Parse(reader.Value.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Could you try if the following implementation of ReadJson works as you expect:
public override object ReadJson(
JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return BigDecimal.Parse(token.ToString());
}
Update
Unfortunately the above won't work. There seems to be no way to read the raw string from the JSON data.
Also note that in my tests the assert for stringValue fails first. See this working example: https://dotnetfiddle.net/s0pqg3
I assume this is because Json.NET internally immediately parses any number token it encounters according to the specified FloatParseHandling. The raw data is never preserved.
I think the only solution is to wrap the big decimal string in quotes like so:
"bigDecimalValue" : "0.0050000012852251529693603515625"
Here is a working example that does exactly that in order to preserve the desired precision: https://dotnetfiddle.net/U1UG3z
I want to deserialize date like this "20160101000000000" to DateTime in UTC kind.
var data = "20160101000000000";
var dateTime = JsonConvert.DeserializeObject<DateTime>(data,
new IsoDateTimeConverter { DateTimeFormat = "yyyyMMddhhmmssfff"});
Try following
var data = "20160101000000000";
var dateTime = new DateTime(JsonConvert.DeserializeObject<Int64>(data));
If you want to UTC format, try following
var utc = dateTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
Or implement Custom Datetime Converter
public class CustomDateTimeConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ return; }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new DateTime(Convert.ToInt64(reader.Value));
}
}
And use like this
var data = "20160101000000000";
var dateTime = JsonConvert.DeserializeObject<DateTime>(data, new CustomDateTimeConverter());
Remember that in JSON, strings must be quoted. Therefore what you've got in data is actually not a JSON string (it's a number). If you want JSON.NET to parse this the way you expect, you'll need to wrap it in quotes:
var data = "\"20160101000000000\"";