Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deserializing dates with jsonsubtypes does not match same result values as stock newtonsoft #166

Closed
techfg opened this issue Apr 17, 2023 · 0 comments · Fixed by #167
Closed
Labels
Milestone

Comments

@techfg
Copy link
Contributor

techfg commented Apr 17, 2023

As demonstrated in dotnetfiddle, the values that result for DateTime/DateTimeOffset fields when deserialized using JsonSubtypes are different than using stock Newtonsoft converter.

JsonSubtypes provides an excellent way for handling inheritance, however the method by which it does this should not result in the target object values being different than when using stock deserialization. For example, when OrderBase uses JsonSubtype and Order does not and there is a DateTime and DateTimeOffset property on each object, the values should be identical across subtype and stock:

var subtype = JsonConvert.DeserializeObject<OrderBase>(json);
var stock = JsonConvert.DeserializeObject<Order>(json);

The reason for the difference is that JsonSubtypes uses a two-step process in order to determine the type to deserialize to: 1) Deserialize to JObject; and then 2) Deserialize to target type. Step 1 should not perform any deserialization itself and the original json should effectively pass thru untouched since its only purpose is to obtain the target type. Step 2 should be responsible for the deserialization. Currently, some deserialization occurs in Step 1 around dates which leads to the behavior difference from stock deserialization.

When deserializing to JObject, since there isn't a known target type for a property, the JToken for a property that "looks like" (see here for history) a DateTime/DateTimeOffset will become a JToken with type Date and the value will be either DateTime or DateTimeOffset depending on JsonSerializerSettings.DateParseHandling value (see default case of JsonTextReader). At this point, the value is already typed so when it passes through step 2 of deserialization, it's simply converted to the target property type from the known class type rather than converted from its raw value to the target property type like would be the case in stock deserialization.

In stock deserialization to a type, the type information is used as strings that "look like" DateTime/DateTimeOffset are handled according to their target type (see here and here). This avoids having to rely on DateParseHandling value.

In v1.8, a fix (#113) was made to address this, however it was removed in v2.0 (#120) with the recommended solution being to specify DateTimeOffset for DateParseHandling. Unfortunately, even specifying DateTimeOffset does not address the underlying issue because the problem just shifts to having DateTime values different than stock deserilization to DateTime values.

Source/destination types

  [JsonConverter(typeof(JsonSubtypes), "discriminator")]
  [JsonSubtypes.KnownSubType(typeof(SubTypeOrder), "SubTypeOrder")]
  public class OrderBase  {  }

  public class SubTypeOrder : OrderBase
  {
      public DateTime DateTime { get; set; }
      public DateTimeOffset DateTimeOffset { get; set; }	
  }

  public class Order
  {
      public DateTime DateTime { get; set; }
      public DateTimeOffset DateTimeOffset { get; set; }		
  }

Source/destination JSON

  { 
    "discriminator": "SubTypeOrder",
    "DateTime": "2011-09-14T00:00:00-04:00",
    "DateTimeOffset": "2011-09-14T00:00:00-04:00"
  }

Expected behavior

Date & DateTimeOffset values are identical when deserializing SubTypeOrder (via JsonSubtypes) and Order (via stock Newtonsoft)

Actual behavior

  1. When using JsonSerializerSettings defaults
    SubTypeOrder.DateTimeOffset: 09/14/2011 04:00:00 +00:00
    Order.DateTimeOffset: 09/14/2011 00:00:00 -04:00

  2. When using recommended solution from #120 (passing DateParseHandling.Offset)
    SubTypeOrder.DateTime: 09/14/2011 00:00:00
    Order.DateTime: 09/14/2011 04:00:00

Steps to reproduce

See dotnetfiddle

var subtype = JsonConvert.DeserializeObject<OrderBase>(json);
var stock = JsonConvert.DeserializeObject<Order>(json);
Console.WriteLine(((SubTypeOrder)subtype).DateTime);
Console.WriteLine(stock.DateTime);
Console.WriteLine(((SubTypeOrder)subtype).DateTimeOffset);
Console.WriteLine(stock.DateTimeOffset);

var settings = new JsonSerializerSettings() { DateParseHandling = DateParseHandling.DateTimeOffset };
subtype = JsonConvert.DeserializeObject<OrderBase>(json, settings);
stock = JsonConvert.DeserializeObject<Order>(json, settings);
Console.WriteLine(((SubTypeOrder)subtype).DateTime);
Console.WriteLine(stock.DateTime);			
Console.WriteLine(((SubTypeOrder)subtype).DateTimeOffset);
Console.WriteLine(stock.DateTimeOffset);
techfg added a commit to techfg/JsonSubTypes that referenced this issue Apr 17, 2023
@manuc66 manuc66 added the bug label Jun 25, 2023
@manuc66 manuc66 added this to the 3.0 milestone Jun 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants