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;
    }
}