Search code examples
c#json.netdatetime-formatjson-deserialization

Newtonsoft Json converts datetime format when deserializing to string


Serializer settings:

jsonSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
jsonSettings.DateFormatString = "yyyy-MM-ddTHH:mm:ss.fffffffZ";
jsonSettings.DateParseHandling = DateParseHandling.DateTimeOffset;
jsonSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;

Original json:

{
    "value": "someData",
    "startDate": "2021-01-16T00:00:00.000Z",
    "endDate": "2021-01-18T00:00:00.000Z"
}

Class that I'm deserializing to:

public class MyData
{
    [JsonConstructor]
    public MyData(string value, string startDate, string endDate)
    {
        this.Value = value;
        this.StartDate = startDate;
        this.EndDate = endDate;
    }

    public string Value { get; }
    public string StartDate { get; }
    public string EndDate { get; }
}

Deserialization call:

using (FileStream fileStream = File.OpenRead(jsonFilePath))
using (StreamReader streamReader = new StreamReader(fileStream))
using (JsonTextReader jsonReader = new JsonTextReader(streamReader))
{
    return this.JsonSerializer.Deserialize(jsonReader, typeof(MyData));
}

Okay, now ideally I would have hoped the deserializer would be smart enough to say, "Okay, I recognize that the value of this 'startDate' field in the input json is a DateTime string, but since the field I'm deserializing it to in the target type is a string, and not a DateTime or DateTimeOffset, I'm just going to leave the string alone and populate the corresponding fields with the exact same string as the input."

However, even if it did decide to convert the string to a DateTimeOffset object, then convert it back to a string during the deserialization process, shouldn't it use the explicitly provided DateFormatString in the settings for the deserialized value? Instead, this is what I'm seeing for the values of the StartDate and EndDate fields in my MyData instance:

myData.startDate == "01/16/2021 00:00:00 +00:00"
myData.endDate == "01/18/2021 00:00:00 +00:00"

Now before you mention it, yes I know I could just set the DateParseHandling setting to DateParseHandling.None, but these serializer setting are used not just for this one class but for lot's of other existing classes, and I don't know if making that change might adversely affect the behavior of some other part of the code.

So is there any way to tell the serializer, to use these settings when deserializing explicitly to DateTime or DateTimeOffset objects, or when deserializing to an arbitrary object without a defined type, but when deserializing explicitly to string fields to leave the input datetime strings unchanged?

Or, failing that, ist there a way to just tell the deserializer to explicitly use the specified DateFormatString when deserializing datetime strings to fields of type string?


Solution

  • So it turns out the problem was with a custom converter we are using for some outer classes that first parses the whole object to a JObject, and then does further deserialization from that JObject. I don't want to make too dramatic of changes to this custom converter as it may adversely affect the behavior of other parts of the codebase. So here is what I'm doing as a workaround.

    First, I moved the date/time format string to a public constant:

    public const string DefaultDateFormatString = "yyyy-MM-ddTHH:mm:ss.fffffffZ";
    

    Then I modified the constructors on the class I was having problems with to explicitly set the date strings to my required format:

    public class MyData
    {
        public MyData(string value, string startDate, string endDate)
        {
            this.Value = value;
            this.StartDate = startDate;
            this.EndDate = endDate;
        }
    
        [JsonConstructor]
        public MyData(string value, DateTimeOffset startDate, DateTimeOffset endDate)
            : this(
                value, 
                startDate.ToString(JsonUtility.DefaultDateFormatString),
                endDate.ToString(JsonUtility.DefaultDateFormatString))
        {
        }
    
        public string Value { get; }
        public string StartDate { get; }
        public string EndDate { get; }
    }