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),
};
}