12

I want to support deserializing more than one custom DateTime format with the Newtonsoft Json deserializer, so I am using IsoDateTimeConverter:

var serializeSettings = new JsonSerializerSettings();
serializeSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyyMMddTHHmmssZ" });

As the DateTimeFormat property does not accept an array of formats, I tried the following to support multiple custom date formats:

var serializeSettings = new JsonSerializerSettings();
serializeSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyyMMddTHHmmssZ" });
serializeSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-ddTHH:mm" });

However the above code's result is supporting deserializing only the first format.

How can I achieve supporting multiple custom DateTime formats?

5
  • Why do you need to support multiple DateTimeFormats at the same time? Commented Jul 13, 2018 at 6:20
  • because I am expecting a request from external components I can't control with multiple datetime formats Commented Jul 13, 2018 at 6:21
  • so, your clients requesting your service in formats they like and that you can't control? shouldn't you just bump them with 400 or 422 status code? I mean, isn't this "bad-by-design"? Your clients must follow your service rules and not vice versa. What if tomorow some of your clients (that you can't control) will decide to use YAML instead of JSON? Commented Jul 13, 2018 at 6:29
  • I can't control what formats they will choose from the ones I support, simply I want to support more than one format! what is so strange about it!!! Commented Jul 13, 2018 at 6:31
  • so, you actualy have some sort of control over your client? (as you say "... what formats they will choose from the ones I support"). Why not to say "Hey, clients! I support only SortableDateTime for DateTime!" Commented Jul 13, 2018 at 6:35

2 Answers 2

16

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

Sign up to request clarification or add additional context in comments.

4 Comments

Its very late.. :) But does it work same in Web API?? Because I tried this in web api. The converter code is not firing or break point within the converter not hitting at all
Yes, this converter should work fine in Web API. If you are having trouble, I would recommend posting a new question, so we can see how you are using the converter.
I posted a question. Please have a look into it stackoverflow.com/questions/68496044/…
It looks like @dbc already found the problem. You are using DateTime? (nullable) in your model class, but the converter as written only handles DateTime (non-nullable). If you modify the converter to handle DateTime? in addition to DateTime, it should work as expected. Btw, I modified my answer above to support both flavors.
2

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.