diff --git a/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs b/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs
index 9f6fdcf577..d26f886b01 100644
--- a/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs
+++ b/src/Stripe.net/Infrastructure/JsonConverters/STJUnixDateTimeConverter.cs
@@ -5,6 +5,66 @@ namespace Stripe.Infrastructure
using System.Text.Json;
using System.Text.Json.Serialization;
+ ///
+ /// A JsonConverterFactory that handles both DateTime and DateTime? types.
+ /// This factory creates specialized converters for each type to properly handle
+ /// null values for nullable DateTime properties (fixes issue #3157).
+ ///
+#pragma warning disable SA1649 // File name should match first type name
+ internal class STJUnixDateTimeConverter : JsonConverterFactory
+#pragma warning restore SA1649 // File name should match first type name
+ {
+ ///
+ public override bool CanConvert(Type typeToConvert)
+ {
+ return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?);
+ }
+
+ ///
+ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (typeToConvert == typeof(DateTime?))
+ {
+ return new STJUnixNullableDateTimeConverter();
+ }
+
+ return new STJUnixDateTimeConverterImpl();
+ }
+ }
+
+ ///
+ /// Converter for nullable DateTime? that properly handles null JSON values.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ internal class STJUnixNullableDateTimeConverter : JsonConverter
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ private static readonly STJUnixDateTimeConverterImpl BaseConverter = new STJUnixDateTimeConverterImpl();
+
+ ///
+ public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.Null)
+ {
+ return null;
+ }
+
+ return BaseConverter.Read(ref reader, typeToConvert, options);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
+ {
+ if (value == null)
+ {
+ writer.WriteNullValue();
+ return;
+ }
+
+ BaseConverter.Write(writer, value.Value, options);
+ }
+ }
+
///
/// Converts a to and from Unix epoch time.
///
@@ -13,7 +73,9 @@ namespace Stripe.Infrastructure
/// Newtonsoft.Json 11.0. Once we bump the minimum version of Newtonsoft.Json to 11.0, we can
/// start using the provided converter and get rid of this class.
///
- internal class STJUnixDateTimeConverter : JsonConverter
+#pragma warning disable SA1402 // File may only contain a single type
+ internal class STJUnixDateTimeConverterImpl : JsonConverter
+#pragma warning restore SA1402 // File may only contain a single type
{
///
/// Reads the JSON representation of the object.
@@ -24,7 +86,6 @@ internal class STJUnixDateTimeConverter : JsonConverter
/// The object value.
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- bool nullable = IsNullable(typeToConvert);
long seconds;
if (reader.TokenType == JsonTokenType.Number)
@@ -73,16 +134,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso
/// The calling serializer's options.
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
- long seconds;
-
- if (value is DateTime dateTime)
- {
- seconds = (long)(dateTime.ToUniversalTime() - DateTimeUtils.UnixEpoch).TotalSeconds;
- }
- else
- {
- throw new JsonException("Expected date object value.");
- }
+ long seconds = (long)(value.ToUniversalTime() - DateTimeUtils.UnixEpoch).TotalSeconds;
if (seconds < 0)
{
@@ -91,21 +143,6 @@ public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializer
writer.WriteNumberValue(seconds);
}
-
- private static bool IsNullable(Type t)
- {
- if (t == null)
- {
- throw new ArgumentNullException(nameof(t));
- }
-
- if (t.IsValueType)
- {
- return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
- }
-
- return true;
- }
}
}
#endif
diff --git a/src/Stripe.net/Infrastructure/JsonConverters/SerializablePropertyCache.cs b/src/Stripe.net/Infrastructure/JsonConverters/SerializablePropertyCache.cs
index bacfe7b40c..503c6df848 100644
--- a/src/Stripe.net/Infrastructure/JsonConverters/SerializablePropertyCache.cs
+++ b/src/Stripe.net/Infrastructure/JsonConverters/SerializablePropertyCache.cs
@@ -69,6 +69,7 @@ internal class SerializablePropertyInfo
private static MethodInfo createGetDelegateMethod = typeof(SerializablePropertyInfo).GetMethod("CreateGetDelegate", BindingFlags.Static | BindingFlags.NonPublic);
private static MethodInfo createSetDelegateMethod = typeof(SerializablePropertyInfo).GetMethod("CreateSetDelegate", BindingFlags.Static | BindingFlags.NonPublic);
private static MethodInfo getConverterForTypeMethod = typeof(SerializablePropertyInfo).GetMethod("GetConverterForType", BindingFlags.Static | BindingFlags.NonPublic);
+ private static MethodInfo getConverterFromFactoryMethod = typeof(SerializablePropertyInfo).GetMethod("GetConverterFromFactory", BindingFlags.Static | BindingFlags.NonPublic);
private static MethodInfo getDefaultConverterMethod = typeof(SerializablePropertyInfo).GetMethod("GetDefaultConverter", BindingFlags.Static | BindingFlags.NonPublic);
private Func