diff --git a/src/Stripe.net/Infrastructure/JsonConverters/STJEmptyableConverter.cs b/src/Stripe.net/Infrastructure/JsonConverters/STJEmptyableConverter.cs new file mode 100644 index 0000000000..bba884dfff --- /dev/null +++ b/src/Stripe.net/Infrastructure/JsonConverters/STJEmptyableConverter.cs @@ -0,0 +1,77 @@ +#if NET6_0_OR_GREATER +namespace Stripe.Infrastructure +{ + using System; + using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; + + /// + /// Converts a to and from JSON. + /// + /// Type of the field when expanded. + internal class STJEmptyableConverter : JsonConverter> + { + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer's options. + public override void Write(Utf8JsonWriter writer, Emptyable value, JsonSerializerOptions options) + { + switch (value) + { + case null: + break; + + case Emptyable emptyable: + if (emptyable.Empty) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, emptyable.Value, options); + } + + break; + + default: + throw new JsonException(string.Format( + "Unexpected value when converting Emptyable. Expected Emptyable, got {0}.", + value.GetType())); + } + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + if (!objectType.IsGenericType) + { + return false; + } + + return typeof(IEmptyable).GetTypeInfo().IsAssignableFrom(objectType.GetGenericTypeDefinition()); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The calling serializer's options. + /// The object value. + public override Emptyable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotSupportedException("Cannot deserialize Emptyable objects."); + } + } +} +#endif diff --git a/src/Stripe.net/Services/_common/Emptyable.cs b/src/Stripe.net/Services/_common/Emptyable.cs index a0385d00f8..04da6f8583 100644 --- a/src/Stripe.net/Services/_common/Emptyable.cs +++ b/src/Stripe.net/Services/_common/Emptyable.cs @@ -1,8 +1,8 @@ namespace Stripe { - /// Represents a generic expandable field. - /// Type of the field when expanded. - internal class Emptyable + /// Represents a field that might be emptyble. + /// Type of the field when not empty. + internal class Emptyable : IEmptyable { private bool empty; private T value; @@ -29,5 +29,9 @@ public bool Empty } } } + + /// Gets or sets the expanded object. + /// The expanded object. + object IEmptyable.Value => this.Value; } } diff --git a/src/Stripe.net/Services/_interfaces/IEmptyable.cs b/src/Stripe.net/Services/_interfaces/IEmptyable.cs new file mode 100644 index 0000000000..26205777da --- /dev/null +++ b/src/Stripe.net/Services/_interfaces/IEmptyable.cs @@ -0,0 +1,25 @@ +namespace Stripe +{ + /// + /// Represents a value that may be of one of several different types. + /// + public interface IEmptyable + { + /// Gets whether or not the field is empty. + /// True if the value is empty; false if the value is set. + public bool Empty { get; } + + /// Gets the value of the current object. + /// The value of the current object. + object Value { get; } + } + + /// Represents a generic expandable field. + /// Type of the field when expanded. + public interface IEmptyable : IEmptyable + { + /// Gets the value of the current object. + /// The value of the current object. + new T Value { get; set; } + } +} diff --git a/src/StripeTests/Wholesome/NewtonsoftAndSystemTextJsonOutputTheSameObject.cs b/src/StripeTests/Wholesome/NewtonsoftAndSystemTextJsonOutputTheSameObject.cs index 67446f36f9..e4b0f155fb 100644 --- a/src/StripeTests/Wholesome/NewtonsoftAndSystemTextJsonOutputTheSameObject.cs +++ b/src/StripeTests/Wholesome/NewtonsoftAndSystemTextJsonOutputTheSameObject.cs @@ -81,11 +81,6 @@ public void Check() continue; } - if (stripeClass.Name.StartsWith("V1Billing")) - { - Debugger.Break(); - } - if (stripeClass.IsGenericType) { // Handle generic types (container types) separately, because diff --git a/src/StripeTests/Wholesome/SystemTextJsonTestUtils.cs b/src/StripeTests/Wholesome/SystemTextJsonTestUtils.cs index 4bf39965b6..21800921e9 100644 --- a/src/StripeTests/Wholesome/SystemTextJsonTestUtils.cs +++ b/src/StripeTests/Wholesome/SystemTextJsonTestUtils.cs @@ -115,6 +115,11 @@ public static Tuple HasCorrectConverterType(Type type, MemberInf { expectedConverterType = typeof(STJUnixDateTimeConverter); } + else if (typeof(IEmptyable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) + { + expectedConverterType = typeof(STJEmptyableConverter<>); + expectedGenericTypeArguments = type.GenericTypeArguments; + } else if (typeof(IAnyOf).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) { expectedConverterType = typeof(STJAnyOfConverter);