Supporting multiple custom DateTime formats when deserializing with Json.Net
If you want to handle multiple possible date formats, you will need to make a custom JsonConverter
which can accept multiple format strings and try them all until one succeeds. Here is a simple example:
class MultiFormatDateConverter : JsonConverter
{
public List<string> DateTimeFormats { get; set; }
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)
{
string dateString = (string)reader.Value;
if (dateString == null)
{
if (objectType == typeof(DateTime?))
return null;
throw new JsonException("Unable to parse null as a date.");
}
DateTime date;
foreach (string format in DateTimeFormats)
{
// adjust this as necessary to fit your needs
if (DateTime.TryParseExact(dateString, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
return date;
}
throw new JsonException("Unable to parse \"" + dateString + "\" as a date.");
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then you can add it to your settings like this:
var settings = new JsonSerializerSettings();
settings.DateParseHandling = DateParseHandling.None;
settings.Converters.Add(new MultiFormatDateConverter
{
DateTimeFormats = new List<string> { "yyyyMMddTHHmmssZ", "yyyy-MM-ddTHH:mm" }
});
Fiddle: https://dotnetfiddle.net/vOpMEY
I want to propose version which supports both DateTime and DateTimeOffset and nullability as well.
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
internal class MultiFormatDateConverter : DateTimeConverterBase
{
public IList<string> DateTimeFormats { get; set; } = new[] { "yyyy-MM-dd" };
public DateTimeStyles DateTimeStyles { get; set; }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var val = IsNullableType(objectType);
if (reader.TokenType == JsonToken.Null)
{
if (!val)
{
throw new JsonSerializationException(
string.Format(CultureInfo.InvariantCulture, "Cannot convert null value to {0}.", objectType));
}
}
Type underlyingObjectType = val ? Nullable.GetUnderlyingType(objectType)! : objectType;
if (reader.TokenType == JsonToken.Date)
{
if (underlyingObjectType == typeof(DateTimeOffset))
{
if (!(reader.Value is DateTimeOffset))
{
return new DateTimeOffset((DateTime)reader.Value);
}
return reader.Value;
}
if (reader.Value is DateTimeOffset)
{
return ((DateTimeOffset)reader.Value).DateTime;
}
return reader.Value;
}
if (reader.TokenType != JsonToken.String)
{
var errorMessage = string.Format(
CultureInfo.InvariantCulture,
"Unexpected token parsing date. Expected String, got {0}.",
reader.TokenType);
throw new JsonSerializationException(errorMessage);
}
var dateString = (string)reader.Value;
if (underlyingObjectType == typeof(DateTimeOffset))
{
foreach (var format in this.DateTimeFormats)
{
// adjust this as necessary to fit your needs
if (DateTimeOffset.TryParseExact(dateString, format, CultureInfo.InvariantCulture, this.DateTimeStyles, out var date))
{
return date;
}
}
}
if (underlyingObjectType == typeof(DateTime))
{
foreach (var format in this.DateTimeFormats)
{
// adjust this as necessary to fit your needs
if (DateTime.TryParseExact(dateString, format, CultureInfo.InvariantCulture, this.DateTimeStyles, out var date))
{
return date;
}
}
}
throw new JsonException("Unable to parse \"" + dateString + "\" as a date.");
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public static bool IsNullableType(Type t)
{
if (t.IsGenericTypeDefinition || t.IsGenericType)
{
return t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
return false;
}
}