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);
}
}
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
The following code will throw an exception:
class SimpleClassWithRegex
{
public Regex RegProp { get; set; }
}
[TestMethod]
public void RegexTest()
{
string json = JsonConvert.SerializeObject(new SimpleClassWithRegex {RegProp = null});
// json = {"RegProp":null}
SimpleClassWithRegex obj = JsonConvert.DeserializeObject<SimpleClassWithRegex>(json);
//Above line throws a JsonSerializationException
}
This seems like strange behavior to me, can someone explain why this isn't a bug? Or perhaps suggest a workaround?
Instantiating a Regex object in place of the null will of course stop this from throwing an exception.
The exception produced is:
Newtonsoft.Json.JsonSerializationException: Unexpected token when reading Regex. Path 'RegProp', line 1, position 15.
UPDATE
It appears that this issue was fixed in release 10.0.1 (March 2017).
This looks like a bug in Json.Net's RegexConverter class. The ReadJson method looks like this:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
return ReadRegexObject(reader, serializer);
}
if (reader.TokenType == JsonToken.String)
{
return ReadRegexString(reader);
}
throw JsonSerializationException.Create(reader, "Unexpected token when reading Regex.");
}
As you can see, it is not expecting or checking for a null token from the reader, so it is falling through to the line which throws a JsonSerializationException.
You may want to report an issue linking back to this SO question.
As a workaround, you can derive your own converter from the RegexConverter and add the check:
public class ImprovedRegexConverter : RegexConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
When you deserialize, pass an instance of your custom converter to the DeserializeObject method like this:
SimpleClassWithRegex obj =
JsonConvert.DeserializeObject<SimpleClassWithRegex>(json, new ImprovedRegexConverter());
Alterntatively, you can add a [JsonConverter] attribute to your Regex class members like this:
class SimpleClassWithRegex
{
[JsonConverter(typeof(ImprovedRegexConverter))]
public Regex RegProp { get; set; }
}
Fiddle: https://dotnetfiddle.net/BIqmd6
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\"";
In my ASP.NET Web API application I'm building a custom JsonConverter to convert any DateTime string in Json objects to DateTimeOffset, using the logged in User's offset.
I have read in this article that the IPrincipal is being reset during the serialization process.
A solution was offered in this article, which I implemented in my application.
What the author failed to mention is how to actually retrieve the IPrincipal inside of the ReadJson() method of the JsonConverter.
So I am currently stuck here.
internal class CustomDateTimeOffsetConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
if (value is DateTimeOffset)
{
var date = (DateTimeOffset)value;
value = date.LocalDateTime;
}
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
if (existingValue is DateTimeOffset)
{
// How to get the current logged in IPrincipal?
// var user = ...
}
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Try this
var requestContext = System.Web.DynamicData.DynamicDataRouteHandler.GetRequestContext(System.Web.HttpContext.Current);
var user = requestContext.HttpContext.User;
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"));
}
}