Force.com-Toolkit-for-NET icon indicating copy to clipboard operation
Force.com-Toolkit-for-NET copied to clipboard

ForceClient Query methods going through HttpGetAsync in JsonHttpClient lose correct offset in DateTimeOffset objects by use of JObject.Parse

Open jamesmanning opened this issue 6 years ago • 2 comments

I'm querying User objects and using json classes with DateTimeOffset fields. The strings coming back from salesforce in the json include explicit offset info (UTC in my case), but due to the current deserialization round-tripping through JObject.Parse (which uses DateTime instead of DateTimeOffset on such strings), the correct timezone offset is lost and you get your local offset instead. The time is still correct, but we've lost the offset info that was in the json we got back from the API call.

https://github.com/developerforce/Force.com-Toolkit-for-NET/blob/03a0e068032111263a3e04b1069569d4ef219358/src/CommonLibrariesForNET/JsonHttpClient.cs#L52-L60

This problem doesn't happen if you just use JsonConvert.DeserializeObject directly (like what the catch clause does) so ideally we just do that instead of the JObject round trip which causes this problem?

Hitting this using ForceClient will happen with any query method, but an example call (the Dump call is just for displaying in linqpad, it doesn't affect the object) is:

var user = await forceClient.QueryByIdAsync<User>("User", "0051U000000234WQAU");
user.Dump("from api");

using a simple POCO of

public class User
{
    public string Id { get; set; }
    public DateTimeOffset CreatedDate { get; set; }
}

You can also see the 'timezone offset lost in JObject roundtrip' behavior manually:

    var testJson = @"
		{
			""Id"": ""0051U000000123WQAU"",
			""CreatedDate"": ""2018-12-04T15:05:29.000+0000"",
		}
    ";
    
    var deserializedUser = JsonConvert.DeserializeObject<User>(testJson);
    deserializedUser.Dump();

    var jobject = JObject.Parse(testJson);
    var jobjectJson = jobject.ToString().Dump("JObject.Parse().ToString()");
    var deserializedViaJObjectUser = JsonConvert.DeserializeObject<User>(jobjectJson);
    deserializedUser.Dump();

This Json.Net behavior isn't well documented but has been mentioned before and seems to be intentional (although I'm going to try to file a new explicit bug about it just to confirm)

https://github.com/JamesNK/Newtonsoft.Json/issues/1110

If the JObject round trip is needed for some reason, could the code add a comment (and ideally unit test that would break if I removed that round trip) to explain why? It certainly doesn't seem to be helping and only hurting in this particular scenario, so it'd be great to understand if there was a scenario where this round trip was necessary.

Thanks!

jamesmanning avatar Feb 22 '19 18:02 jamesmanning

Also filed a bug on Json.Net just to attempt to definitively establish whether the JObject.Parse behavior is By Design or whether it should preserve offset (presumably by deserializing into a DateTimeOffset instead of a DateTime)

https://github.com/JamesNK/Newtonsoft.Json/issues/1993

This should help at least determine whether Json.Net is behaving as expected or not. I still think the JsonHttpClient code should just use JsonConvert.DeserializeObject<T> directly since the JObject.Parse doesn't seem to provide any value (and causes this breakage), though.

jamesmanning avatar Feb 22 '19 19:02 jamesmanning

I also face similar behavior. I have to manually look at CreatedDate/LastModifiedDate's Kind field and change datetime to UTC.

Tennyleaz avatar Apr 22 '19 03:04 Tennyleaz