5

I've tried deserializing a Json string containing DateTime.MinValue in every conceivable way, but when the set method gets called on my object. The date always gets changed from -01-01-01- to -01-01-02-.

The Json being parsed clearly contains

"inception_date": "0001-01-01T00:00:00+00:00"

I then call JsonConvert on it:

return JsonConvert.DeserializeObject<T>(json, deserializerSettings);

Where T is a basic struct that contains a property: DateTime inception_date { get; set; } property. The deserializer settings are as follows:

deserializerSettings = new JsonSerializerSettings()
{
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    DateParseHandling = Newtonsoft.Json.DateParseHandling.None,
    DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc
};

Yet somewhere in the bowels of Newtonsoft.Json.dll, the above time gets converted to the following jObject.

"inception_date": "0001-01-02T00:00:00Z"

I have no way of stepping into their code, but by the damage is done before my code sees another call. ReadJson in the JsonConverter calls serializer.Populate(jObject.CreateReader(), target); where target is an instance of my class T, and jObject has somehow been rendered with the above incorrect date.

Can anyone figure out why this is happening or how I can prevent it? The jObject seems to be getting created in a way that is ignoring my serializer settings, which explicitly say not to screw with the date string (DateParseHandling.None).


I've taken screen shots to illustrate exactly where Newtonsoft's JsonConvert method seems to have lost a vital configuration value.

As you can see, this is the point in the code where I call JsonConvert:

enter image description here

The dateParseHandling value is set to None, and that's how it should be for this to work.

At this next step, I've jumped a few internal Newtonsoft calls and landed in a generic implementation of the JsonConverter which I borrowed from an accepted reference implementation to be able to see what was going on. The JsonReader passed in suddenly has lost that dateParseHandling value:

enter image description here

Because of this value being switched back to DateTime - the internal workings of the jObject try to represent this as an internal localized DateTime, which underflows because my timezone is negative, and we're already representing the minimum DateTime value, resulting in the conversion back to UTC adding a full day.

Community
  • 1
  • 1
Alain
  • 26,663
  • 20
  • 114
  • 184
  • Similar to your previous [question](http://stackoverflow.com/questions/21613644/why-is-the-json-net-parser-automatically-localizing-the-dates-its-parsing) (JObject.Parse), why do you use `serializer.Populate`. Use `JsonConvert.SerializeObject` with appropriate *settings* . It is all related with how to use *Swiss army knife* – L.B Feb 06 '14 at 23:14
  • According to the docs for Json.Net, you shouldn't need any serializer settings for the example datetime you provided. Have you tried it without any of the extra settings? – Pete Garafano Feb 06 '14 at 23:18
  • @PeteGarafano yeah, the documentation is definitely misleading. If you don't use any settings it uses your locale by default. – Alain Feb 06 '14 at 23:19
  • @L.B As you can see in my first screenshot, I am definitely using `JsonConvert.SerializeObject` with the appropriate settings, but it was putting the wrong Date into my object. I introduced the override of the `JsonConverter` using the `JsonCreationConverter` posted on line so that I could add a breakpoint deeper down and see what was going on. – Alain Feb 06 '14 at 23:21
  • @Alain Sorry but questions supported with images are not much readable. – L.B Feb 06 '14 at 23:25

1 Answers1

3

Try:

deserializerSettings = new JsonSerializerSettings()
{
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset
}

This resulted in me getting 1/1/0001 12:00:00 AM instead of 1/2/0001 12:00:00 AM

Here is my test code (written in LINQPad)

void Main()
{
    var deserializerSettings = new JsonSerializerSettings()
    {
        DateFormatHandling = DateFormatHandling.IsoDateFormat,
        DateParseHandling = Newtonsoft.Json.DateParseHandling.None,
        DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc
    };
    var json = "{\"inception_date\": \"0001-01-01T00:00:00+00:00\"}";
    var parsedObj = JsonConvert.DeserializeObject<TestClass>(json, deserializerSettings);
    Console.WriteLine(parsedObj);

    deserializerSettings = new JsonSerializerSettings()
    {
        DateFormatHandling = DateFormatHandling.IsoDateFormat,
        DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset
    };
    parsedObj = JsonConvert.DeserializeObject<TestClass>(json, deserializerSettings);
    Console.WriteLine(parsedObj);
}

public class TestClass
{
    public DateTime inception_date {get;set;}
}

Outputs:

OutputFromProgram

Pete Garafano
  • 4,863
  • 1
  • 23
  • 40
  • This looks promising, but I'm flabbergasted that it's working for you and not me. I'm passing the exact same json: `"{\"expiry_date\": \"0001-01-01T00:00:00+00:00\"}"` But with those configuration options (and no others) I'm getting an exception `InvalidCastException at Newtonsoft.Json.JsonReader.ReadAsDateTimeInternal() in c:\Dev\Releases\Working\Newtonsoft.Json\Src\Newtonsoft.Json\JsonReader.cs:line 613`. I wonder if my version of JSON.Net has a bug there? (V4.5.5.14908) – Alain Feb 07 '14 at 00:55
  • Wow, they're on V6. I had no idea I was so far behind - I thought I got their latest just a few months ago. At least now I can rest easy tonight with a potential resolution in mind. Thanks, and points coming your way if this works! – Alain Feb 07 '14 at 01:00
  • The benefits of NuGet if you can use it in your project, it'll help keep you up to date. – Pete Garafano Feb 07 '14 at 11:56
  • I got the latest but it's still not working. In fact it fails to convert any date with these settings. I took the liberty of downloading the source this time too. "Cannot Unbox (DateTimeOffset) as a 'System.DateTime'. Did you use .NET 4.0 or 4.5 in your tests? I wonder if the way they're relying on implicit conversion isn't working here... `if (TokenType == JsonToken.Date) return (DateTime)Value;` (Newtonsoft.Json.JsonReader.ReadAsDateTimeInternal() Line 708) – Alain Feb 07 '14 at 14:52
  • LINQPad defaults to the latest version of .NET installed on the machine, so I ran against 4.5. What are you running against? Also, what is your culture string so I can try running in your local culture in .NET. – Pete Garafano Feb 07 '14 at 14:56
  • .Net is 4.0. Culture string is "en-CA", current timezone offset is -04:00 – Alain Feb 07 '14 at 15:25
  • I can't seem to replicate your issues, even in .NET 4. I've tried running this through NUnit tests with the `SetCulture` attribute and I get the same result. Just for a sanity check, can you post your POCO, T? – Pete Garafano Feb 07 '14 at 17:28
  • Plain old C# object, you quoted your type as just being `T` above in your question. – Pete Garafano Feb 07 '14 at 18:17
  • I'm using the following class in my example: `public class BasicLayer : Layer { }` `public abstract class Layer { public DateTime inception_date { get; set; } }` – Alain Feb 07 '14 at 18:37
  • Because the base type is abstract, the line `var parsedObj = JsonConvert.DeserializeObject(json, deserializerSettings);` requires a converter to give it an instantiable object type, so I have the simplest possible Converter added in the deserializer settings: `Converters = new JsonConverter[]{ new LayerConverter() }` Where `public class LayerConverter : JsonCreationConverter{ protected override Layer Create(Type objectType, JObject jObject) { return new BasicLayer(); } }` This is the class I referenced above, used all over for parsing json to a runtime determined type. – Alain Feb 07 '14 at 18:40
  • I modified your above example slightly to get it reproducing the error: http://pastebin.com/WRPbz2JS. Seems like it could be that this JsonCreationConverter used widely since Json.Net came out is just wrong. – Alain Feb 07 '14 at 18:41
  • I think I'm narrowing in on the real problem here. It's frankly a complete god damned mystery as to why simply using the base JsonConvert with no additional serializers works, but adding an object type resolver doesn't. Either way, I've narrowed in on the code that makes it not work inside the library - DateTime.TryParse itself is botching the parsing of DateTime.MinValue, so I've posted a followup question here: http://stackoverflow.com/questions/21637556/datetime-tryparse-cannot-parse-datetime-minvalue – Alain Feb 07 '14 at 20:55