zlZimon zlZimon - 3 months ago 52
C# Question

DeserializeObject with DateTime member does not work with Json.Net

I am retrieving a JSON from a webrequest which I would like to deserialize into following class:

public class MyData
{
public int id { get; set; }
public DateTime openTime { get; set; }
MyData() {}
}


This is the JSON I receive from the server:

var json= @"{""Id"": ""12345"",""openTime"":""2015-09-01T12:00:00:000Z""}"


As far as I can tell this DateTime string is ISO 8601 but I do not understand why there are three 0 at the end for milliseconds and an additional Z. This is how I am trying to convert the string to my object:

var responseInstance = JsonConvert.DeserializeObject<MyData>(json,new IsoDateTimeConverter());


This throws a System.FormatException : String was not recognized as a valid DateTime.

When I try to add JsonSerializerSetting instead of the IsoDateTimeConverter like this:

var deserializeSetting = new JsonSerializerSettings()
{
DateFormatHandling = DateFormatHandling.IsoDateFormat
};
var responseInstance = JsonConvert.DeserializeObject<MyData>(json,deserializeSetting);


no Exception is thrown but instead the datetime Member shows always 01.01.0001 00:00:00

Answer

The problem is your date string is not correctly formatted. There should be a period character (.) between the seconds and milliseconds, not a colon (:). Json.Net uses DateTime.Parse internally to parse dates. If it fails, then it silently eats the error, and the date never gets set on your object. Thus you end up with the default date of 01/01/0001 00:00:00.

EDIT

If you cannot change the JSON (e.g. because it is controlled by a third party), you can use a custom JSON converter to allow the bad format to be deserialized correctly.

class BadDateFixingConverter : JsonConverter
{
    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 rawDate = (string)reader.Value;
        DateTime date;

        // First try to parse the date string as is (in case it is correctly formatted)
        if (DateTime.TryParse(rawDate, out date))
        {
            return date;
        }

        // If not, see if the string matches the known bad format. 
        // If so, replace the ':' with '.' and reparse.
        if (rawDate.Length > 19 && rawDate[19] == ':')
        {
            rawDate = rawDate.Substring(0, 19) + '.' + rawDate.Substring(20);
            if (DateTime.TryParse(rawDate, out date))
            {
                return date;
            }
        }

        // It's not a date after all, so just return the default value
        if (objectType == typeof(DateTime?)) 
            return null;

        return DateTime.MinValue;
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Use it like this:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    Converters = new List<JsonConverter> { new BadDateFixingConverter() },
    DateParseHandling = DateParseHandling.None
};

MyData obj = JsonConvert.DeserializeObject<MyData>(json, settings);

Fiddle: https://dotnetfiddle.net/M1w36e