Skip to content

Commit

Permalink
Merge pull request #804 from clement911/date-deserialization-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
nozzlegear authored Nov 3, 2022
2 parents 5e1793b + 34c60bd commit 63570bf
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 30 deletions.
18 changes: 9 additions & 9 deletions ShopifySharp.Tests/InvalidDateToNullConverter_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using ShopifySharp.Converters;
using ShopifySharp.Infrastructure;
using System;
using Xunit;

Expand All @@ -14,12 +15,12 @@ public void SerializeRoundtripValidDates()
DateTime validNow = DateTime.Now;
DateTimeOffset validNowOffset = DateTimeOffset.Now;

string serializedJson = JsonConvert.SerializeObject(new TestObject()
string serializedJson = Serializer.Serialize(new TestObject
{
DateTime = validNow,
DateTimeOffset = validNowOffset,
}, new InvalidDateToNullConverter());
var deserialized = JsonConvert.DeserializeObject<TestObject>(serializedJson, new InvalidDateToNullConverter());
DateTimeOffset = validNowOffset
});
var deserialized = Serializer.Deserialize<TestObject>(serializedJson);

Assert.Equal(validNow, deserialized.DateTime);
Assert.Equal(validNowOffset, deserialized.DateTimeOffset);
Expand All @@ -33,13 +34,12 @@ public void SerializeRoundtripInvalidDates()
{
DateTime = null,
DateTimeOffset = null,
}, new InvalidDateToNullConverter())
.Replace("null", strInvalidISODate);
}).Replace("null", strInvalidISODate);

var deserialized = JsonConvert.DeserializeObject<TestObject>(serializedJson, new InvalidDateToNullConverter());
var deserialized = Serializer.Deserialize<TestObject>(serializedJson);

Assert.Equal(null, deserialized.DateTime);
Assert.Equal(null, deserialized.DateTimeOffset);
Assert.Null(deserialized.DateTime);
Assert.Null(deserialized.DateTimeOffset);
}

class TestObject
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
using Newtonsoft.Json.Converters;
using System;
using Newtonsoft.Json;
using ShopifySharp.Enums;
using System.Runtime.Serialization;
using System.Reflection;
using System.Linq;

namespace ShopifySharp.Converters
{
/// <summary>
/// A custom converter that detects invalid dates and convert them to null instead of throwing an exception.
/// In particular, FulfillmentEvent.EstimatedDeliveryAt has been observed to sometimes contain an invalid date
/// e.g '0000-12-31T18:09:24-05:50', which is smaller than both DateTime.MinValue and DateTimeOffset.MinValue
/// UPDATE October 2022:
/// This API bug has been encountered in several additional API fields, such as transaction.processed_at, customer.accepts_marketing_updated_at and customer.email_marketing_consent.consent_updated_at
/// See https://github.com/nozzlegear/ShopifySharp/issues/803
/// This converter is applied to all date properties because:
/// *It seems to can start occurring on multiple any date fields
/// *The impact of not handling is quite severe. ShopifySharp would fail to deserialize and return an error
/// </summary>
public class InvalidDateToNullConverter : IsoDateTimeConverter
public class InvalidDateConverter : IsoDateTimeConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value != null && reader.Value.ToString().StartsWith("0000"))
if (reader.Value != null && reader.Value.ToString().StartsWith("0000-"))
return null;

return base.ReadJson(reader, objectType, existingValue, serializer);
Expand Down
1 change: 0 additions & 1 deletion ShopifySharp/Entities/Customer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class Customer : ShopifyObject
/// The date and time when the customer consented or objected to receiving marketing material by email. Set this value whenever the customer consents or objects to marketing materials.
/// </summary>
[JsonProperty("accepts_marketing_updated_at")]
[JsonConverter(typeof(InvalidDateToNullConverter))]
[Obsolete("As of API version 2022-04, this property is deprecated. Use email_marketing_consent instead.")]
public DateTimeOffset? AcceptsMarketingUpdatedAt { get; set; }

Expand Down
1 change: 0 additions & 1 deletion ShopifySharp/Entities/FulfillmentEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ public class FulfillmentEvent : ShopifyObject
/// The estimated date of delivery.
/// </summary>
[JsonProperty("estimated_delivery_at")]
[JsonConverter(typeof(InvalidDateToNullConverter))]
public DateTimeOffset? EstimatedDeliveryAt { get; set; }

/// <summary>
Expand Down
26 changes: 17 additions & 9 deletions ShopifySharp/Infrastructure/Serializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Newtonsoft.Json;
using ShopifySharp.Converters;
using System;
using System.Collections.Generic;

namespace ShopifySharp.Infrastructure
{
Expand All @@ -7,18 +10,23 @@ namespace ShopifySharp.Infrastructure
/// </summary>
public static class Serializer
{
public static JsonSerializerSettings Settings { get; } = new JsonSerializerSettings()
private static JsonSerializerSettings CreateSettings()
{
NullValueHandling = NullValueHandling.Ignore
};
return new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.DateTimeOffset,
NullValueHandling = NullValueHandling.Ignore,
Converters = new List<JsonConverter>
{
new InvalidDateConverter()
}
};
}

public static JsonSerializer JsonSerializer { get; } = new JsonSerializer
{
DateParseHandling = DateParseHandling.DateTimeOffset
};
public static string Serialize(object data) => JsonConvert.SerializeObject(data, CreateSettings());

public static string Serialize(object data) => JsonConvert.SerializeObject(data, Settings);
public static T Deserialize<T>(string json) => JsonConvert.DeserializeObject<T>(json, CreateSettings());

public static T Deserialize<T>(string json) => JsonConvert.DeserializeObject<T>(json, Settings);
public static object Deserialize(string json, Type objectType) => JsonConvert.DeserializeObject(json, objectType, CreateSettings());
}
}
5 changes: 1 addition & 4 deletions ShopifySharp/Services/ShopifyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ public abstract class ShopifyService
{
public virtual string APIVersion => "2022-04";

private static JsonSerializer _Serializer = Serializer.JsonSerializer;

private static IRequestExecutionPolicy _GlobalExecutionPolicy = new DefaultRequestExecutionPolicy();

private static IHttpClientFactory _HttpClientFactory = new DefaultHttpClientFactory();
Expand Down Expand Up @@ -237,8 +235,7 @@ protected async Task<RequestResult<T>> ExecuteRequestAsync<T>(RequestUri uri, Ht
// This method may fail when the method was Delete, which is intendend.
// Delete methods should not be parsing the response JSON and should instead
// be using the non-generic ExecuteRequestAsync.
var reader = new JsonTextReader(new StringReader(rawResult));
var data = _Serializer.Deserialize<JObject>(reader).SelectToken(rootElement);
var data = Serializer.Deserialize<JObject>(rawResult).SelectToken(rootElement);
result = data.ToObject<T>();
}
Expand Down

0 comments on commit 63570bf

Please sign in to comment.