using System.Reflection; using Newtonsoft.Json; namespace Idoklad.Infrastructure; /// /// Read support for the SDK's NullableProperty<T>, used by Patch models to tell /// "not set" apart from "set to null". The SDK converter only writes: its ReadJson throws /// , because the SDK never deserializes a Patch model. This /// service does, so a PATCH body carrying such a member failed while binding. Writing stays with /// the SDK converter so the payload sent upstream is unchanged. /// public sealed class SdkNullablePropertyConverter : JsonConverter { private readonly JsonConverter _sdkConverter; public SdkNullablePropertyConverter(JsonConverter sdkConverter) { _sdkConverter = sdkConverter; } public override bool CanRead => true; public override bool CanWrite => _sdkConverter.CanWrite; public override bool CanConvert(Type objectType) => _sdkConverter.CanConvert(objectType); /// /// Reads the JSON scalar the SDK converter writes: a value, or null for an explicit reset. /// A member missing from the body is never read at all and keeps its unset default. /// public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var propertyType = Nullable.GetUnderlyingType(objectType) ?? objectType; var valueType = propertyType.GetGenericArguments()[0]; var value = reader.TokenType == JsonToken.Null ? null : serializer.Deserialize(reader, typeof(Nullable<>).MakeGenericType(valueType)); // The member converter puts the reader in DateParseHandling.None, so the date arrives as a // string and the serializer's DateTimeZoneHandling never gets applied to it. The SDK // rejects a DateTime whose Kind is not Utc, so apply the same normalization here. if (value is DateTime date) { value = ToUtc(date); } // NullableProperty(T? value) marks the member as set, including when the value is null. return Activator.CreateInstance(propertyType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance, null, new[] { value }, null); } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) => _sdkConverter.WriteJson(writer, value, serializer); /// /// Matches : a value that carries an offset is converted, /// a value without one is taken as already being UTC and is only marked as such. /// private static DateTime ToUtc(DateTime value) => value.Kind switch { DateTimeKind.Utc => value, DateTimeKind.Local => value.ToUniversalTime(), _ => DateTime.SpecifyKind(value, DateTimeKind.Utc), }; }