From 34c60bd116c2608378d7014094adaf9544e5ed46 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 2 Nov 2022 11:27:08 +1100 Subject: [PATCH] -Renamed InvalidDateToNullConverter to InvalidDateConverter and applied it globally to all date properties -Simplified the JSON serialization logic --- .../InvalidDateToNullConverter_Tests.cs | 18 ++++++------- ...llConverter.cs => InvalidDateConverter.cs} | 14 +++++----- ShopifySharp/Entities/Customer.cs | 1 - ShopifySharp/Entities/FulfillmentEvent.cs | 1 - ShopifySharp/Infrastructure/Serializer.cs | 26 ++++++++++++------- ShopifySharp/Services/ShopifyService.cs | 5 +--- 6 files changed, 35 insertions(+), 30 deletions(-) rename ShopifySharp/Converters/{InvalidDateToNullConverter.cs => InvalidDateConverter.cs} (57%) diff --git a/ShopifySharp.Tests/InvalidDateToNullConverter_Tests.cs b/ShopifySharp.Tests/InvalidDateToNullConverter_Tests.cs index 11d2313e..9b95946f 100644 --- a/ShopifySharp.Tests/InvalidDateToNullConverter_Tests.cs +++ b/ShopifySharp.Tests/InvalidDateToNullConverter_Tests.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using ShopifySharp.Converters; +using ShopifySharp.Infrastructure; using System; using Xunit; @@ -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(serializedJson, new InvalidDateToNullConverter()); + DateTimeOffset = validNowOffset + }); + var deserialized = Serializer.Deserialize(serializedJson); Assert.Equal(validNow, deserialized.DateTime); Assert.Equal(validNowOffset, deserialized.DateTimeOffset); @@ -33,13 +34,12 @@ public void SerializeRoundtripInvalidDates() { DateTime = null, DateTimeOffset = null, - }, new InvalidDateToNullConverter()) - .Replace("null", strInvalidISODate); + }).Replace("null", strInvalidISODate); - var deserialized = JsonConvert.DeserializeObject(serializedJson, new InvalidDateToNullConverter()); + var deserialized = Serializer.Deserialize(serializedJson); - Assert.Equal(null, deserialized.DateTime); - Assert.Equal(null, deserialized.DateTimeOffset); + Assert.Null(deserialized.DateTime); + Assert.Null(deserialized.DateTimeOffset); } class TestObject diff --git a/ShopifySharp/Converters/InvalidDateToNullConverter.cs b/ShopifySharp/Converters/InvalidDateConverter.cs similarity index 57% rename from ShopifySharp/Converters/InvalidDateToNullConverter.cs rename to ShopifySharp/Converters/InvalidDateConverter.cs index e09f6ab6..122a91f3 100644 --- a/ShopifySharp/Converters/InvalidDateToNullConverter.cs +++ b/ShopifySharp/Converters/InvalidDateConverter.cs @@ -1,10 +1,6 @@ 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 { @@ -12,12 +8,18 @@ namespace ShopifySharp.Converters /// 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 /// - 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); diff --git a/ShopifySharp/Entities/Customer.cs b/ShopifySharp/Entities/Customer.cs index 48835b85..f3b35e7c 100644 --- a/ShopifySharp/Entities/Customer.cs +++ b/ShopifySharp/Entities/Customer.cs @@ -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. /// [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; } diff --git a/ShopifySharp/Entities/FulfillmentEvent.cs b/ShopifySharp/Entities/FulfillmentEvent.cs index d39fdda2..20048a64 100644 --- a/ShopifySharp/Entities/FulfillmentEvent.cs +++ b/ShopifySharp/Entities/FulfillmentEvent.cs @@ -99,7 +99,6 @@ public class FulfillmentEvent : ShopifyObject /// The estimated date of delivery. /// [JsonProperty("estimated_delivery_at")] - [JsonConverter(typeof(InvalidDateToNullConverter))] public DateTimeOffset? EstimatedDeliveryAt { get; set; } /// diff --git a/ShopifySharp/Infrastructure/Serializer.cs b/ShopifySharp/Infrastructure/Serializer.cs index 6c02d632..0cde0373 100644 --- a/ShopifySharp/Infrastructure/Serializer.cs +++ b/ShopifySharp/Infrastructure/Serializer.cs @@ -1,4 +1,7 @@ using Newtonsoft.Json; +using ShopifySharp.Converters; +using System; +using System.Collections.Generic; namespace ShopifySharp.Infrastructure { @@ -7,18 +10,23 @@ namespace ShopifySharp.Infrastructure /// 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 + { + 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(string json) => JsonConvert.DeserializeObject(json, CreateSettings()); - public static T Deserialize(string json) => JsonConvert.DeserializeObject(json, Settings); + public static object Deserialize(string json, Type objectType) => JsonConvert.DeserializeObject(json, objectType, CreateSettings()); } } \ No newline at end of file diff --git a/ShopifySharp/Services/ShopifyService.cs b/ShopifySharp/Services/ShopifyService.cs index 057671e3..934fdd7e 100644 --- a/ShopifySharp/Services/ShopifyService.cs +++ b/ShopifySharp/Services/ShopifyService.cs @@ -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(); @@ -237,8 +235,7 @@ protected async Task> ExecuteRequestAsync(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(reader).SelectToken(rootElement); + var data = Serializer.Deserialize(rawResult).SelectToken(rootElement); result = data.ToObject(); }