diff --git a/Postgrest/Attributes/ColumnAttribute.cs b/Postgrest/Attributes/ColumnAttribute.cs index 3cef106..1008f96 100644 --- a/Postgrest/Attributes/ColumnAttribute.cs +++ b/Postgrest/Attributes/ColumnAttribute.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.CompilerServices; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Supabase.Postgrest.Attributes { @@ -26,7 +26,7 @@ public class ColumnAttribute : Attribute /// /// Specifies what should be serialized in the event this column's value is NULL /// - public NullValueHandling NullValueHandling { get; set; } + public JsonIgnoreCondition NullValueHandling { get; set; } /// /// If the performed query is an Insert or Upsert, should this value be ignored? @@ -39,7 +39,7 @@ public class ColumnAttribute : Attribute public bool IgnoreOnUpdate { get; } /// - public ColumnAttribute([CallerMemberName] string? columnName = null, NullValueHandling nullValueHandling = NullValueHandling.Include, bool ignoreOnInsert = false, bool ignoreOnUpdate = false) + public ColumnAttribute([CallerMemberName] string? columnName = null, JsonIgnoreCondition nullValueHandling = JsonIgnoreCondition.Never, bool ignoreOnInsert = false, bool ignoreOnUpdate = false) { ColumnName = columnName!; // Will either be user specified or given by runtime compiler. NullValueHandling = nullValueHandling; diff --git a/Postgrest/Client.cs b/Postgrest/Client.cs index e5ed0b3..380d59a 100644 --- a/Postgrest/Client.cs +++ b/Postgrest/Client.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json; +using System.Text.Json.Serialization; using Supabase.Core.Extensions; using Supabase.Postgrest.Interfaces; using Supabase.Postgrest.Models; using Supabase.Postgrest.Responses; +using System.Text.Json.Serialization.Metadata; +using System.Reflection; +using Supabase.Postgrest.Converters; namespace Supabase.Postgrest { @@ -15,26 +18,23 @@ namespace Supabase.Postgrest public class Client : IPostgrestClient { /// - /// Custom Serializer resolvers and converters that will be used for encoding and decoding Postgrest JSON responses. + /// Custom Serializer options that will be used for encoding and decoding Postgrest JSON responses. /// - /// By default, Postgrest seems to use a date format that C# and Newtonsoft do not like, so this initial + /// By default, Postgrest seems to use a date format that C# does not like, so this initial /// configuration handles that. /// - public static JsonSerializerSettings SerializerSettings(ClientOptions? options = null) + public static JsonSerializerOptions SerializerOptions(ClientOptions? options = null) { options ??= new ClientOptions(); - return new JsonSerializerSettings + return new JsonSerializerOptions { - ContractResolver = new PostgrestContractResolver(), + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, Converters = { - // 2020-08-28T12:01:54.763231 - new IsoDateTimeConverter - { - DateTimeStyles = options.DateTimeStyles, - DateTimeFormat = ClientOptions.DATE_TIME_FORMAT - } + new JsonStringEnumConverter(), + // new PostgrestDateTimeConverter(options.DateTimeStyles, ClientOptions.DATE_TIME_FORMAT) + new RangeConverter() } }; } @@ -70,7 +70,7 @@ public void RemoveDebugHandler(IPostgrestDebugger.DebugEventHandler handler) => /// /// Function that can be set to return dynamic headers. - /// + /// /// Headers specified in the constructor options will ALWAYS take precedence over headers returned by this function. /// public Func>? GetHeaders { get; set; } @@ -87,10 +87,9 @@ public Client(string baseUrl, ClientOptions? options = null) Options = options ?? new ClientOptions(); } - /// public IPostgrestTable Table() where T : BaseModel, new() => - new Table(BaseUrl, SerializerSettings(Options), Options) + new Table(BaseUrl, SerializerOptions(Options), Options) { GetHeaders = GetHeaders }; @@ -98,18 +97,17 @@ public Client(string baseUrl, ClientOptions? options = null) /// public IPostgrestTableWithCache Table(IPostgrestCacheProvider cacheProvider) where T : BaseModel, new() => - new TableWithCache(BaseUrl, cacheProvider, SerializerSettings(Options), Options) + new TableWithCache(BaseUrl, cacheProvider, SerializerOptions(Options), Options) { GetHeaders = GetHeaders }; - /// public async Task Rpc(string procedureName, object? parameters = null) { var response = await Rpc(procedureName, parameters); - return string.IsNullOrEmpty(response.Content) ? default : JsonConvert.DeserializeObject(response.Content!); + return string.IsNullOrEmpty(response.Content) ? default : JsonSerializer.Deserialize(response.Content!, SerializerOptions(Options)); } /// @@ -120,13 +118,13 @@ public Task Rpc(string procedureName, object? parameters = null) var canonicalUri = builder.Uri.ToString(); - var serializerSettings = SerializerSettings(Options); + var serializerOptions = SerializerOptions(Options); // Prepare parameters Dictionary? data = null; if (parameters != null) - data = JsonConvert.DeserializeObject>( - JsonConvert.SerializeObject(parameters, serializerSettings)); + data = JsonSerializer.Deserialize>( + JsonSerializer.Serialize(parameters, serializerOptions)); // Prepare headers var headers = Helpers.PrepareRequestHeaders(HttpMethod.Post, @@ -137,8 +135,8 @@ public Task Rpc(string procedureName, object? parameters = null) // Send request var request = - Helpers.MakeRequest(Options, HttpMethod.Post, canonicalUri, serializerSettings, data, headers); + Helpers.MakeRequest(Options, HttpMethod.Post, canonicalUri, serializerOptions, data, headers); return request; } } -} \ No newline at end of file +} diff --git a/Postgrest/Converters/DateTimeConverter.cs b/Postgrest/Converters/DateTimeConverter.cs index 28aa3da..ea9a6d1 100644 --- a/Postgrest/Converters/DateTimeConverter.cs +++ b/Postgrest/Converters/DateTimeConverter.cs @@ -1,29 +1,30 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Supabase.Postgrest.Converters { /// - public class DateTimeConverter : JsonConverter + public class DateTimeConverter : JsonConverter { /// - public override bool CanConvert(Type objectType) + public override bool CanConvert(Type typeToConvert) { - throw new NotImplementedException(); + return typeToConvert == typeof(DateTime) || typeToConvert == typeof(List); } /// - public override bool CanWrite => false; - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.Value != null) + if (reader.TokenType == JsonTokenType.String) { - var str = reader.Value.ToString(); + var str = reader.GetString(); + if (str == null) + { + return null; + } var infinity = ParseInfinity(str); @@ -32,38 +33,46 @@ public override bool CanConvert(Type objectType) return (DateTime)infinity; } - var date = DateTime.Parse(str); - return date; + return DateTime.Parse(str); } - var result = new List(); - - try + if (reader.TokenType == JsonTokenType.StartArray) { - var jo = JArray.Load(reader); + var result = new List(); - foreach (var item in jo.ToArray()) + while (reader.Read()) { - var inner = item.ToString(); - - var infinity = ParseInfinity(inner); - - if (infinity != null) + if (reader.TokenType == JsonTokenType.EndArray) { - result.Add((DateTime)infinity); + break; } - var date = DateTime.Parse(inner); - result.Add(date); + if (reader.TokenType == JsonTokenType.String) + { + var inner = reader.GetString(); + if (inner == null) + { + return null; + } + + var infinity = ParseInfinity(inner); + + if (infinity != null) + { + result.Add((DateTime)infinity); + } + else + { + var date = DateTime.Parse(inner); + result.Add(date); + } + } } - } - catch (JsonReaderException) - { - return null; - } + return result; + } - return result; + return null; } private static DateTime? ParseInfinity(string input) @@ -77,7 +86,7 @@ public override bool CanConvert(Type objectType) } /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { throw new NotImplementedException(); } diff --git a/Postgrest/Converters/IntConverter.cs b/Postgrest/Converters/IntConverter.cs index b5e07ec..90a7288 100644 --- a/Postgrest/Converters/IntConverter.cs +++ b/Postgrest/Converters/IntConverter.cs @@ -1,34 +1,53 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Supabase.Postgrest.Converters { /// - public class IntArrayConverter : JsonConverter + public class IntArrayConverter : JsonConverter> { /// - public override bool CanConvert(Type objectType) + public override bool CanConvert(Type typeToConvert) { - throw new NotImplementedException(); + return typeToConvert == typeof(List); } /// - public override bool CanRead => false; - - /// - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + public override List? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new NotImplementedException(); + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException("Expected start of array."); + } + + var list = new List(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + return list; + } + + if (reader.TokenType == JsonTokenType.Number) + { + list.Add(reader.GetInt32()); + } + else + { + throw new JsonException($"Unexpected token type: {reader.TokenType}"); + } + } + + throw new JsonException("Unexpected end of JSON."); } /// - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, List value, JsonSerializerOptions options) { - if (value is List list) - { - writer.WriteValue($"{{{string.Join(",", list)}}}"); - } + writer.WriteStringValue($"{{{string.Join(",", value)}}}"); } } } diff --git a/Postgrest/Converters/RangeConverter.cs b/Postgrest/Converters/RangeConverter.cs index 00749b7..18e9e53 100644 --- a/Postgrest/Converters/RangeConverter.cs +++ b/Postgrest/Converters/RangeConverter.cs @@ -1,33 +1,35 @@ using System; using System.Text.RegularExpressions; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest.Extensions; using Supabase.Postgrest.Exceptions; namespace Supabase.Postgrest.Converters { - /// - /// Used by Newtonsoft.Json to convert a C# range into a Postgrest range. + /// Used by System.Text.Json to convert a C# range into a Postgrest range. /// - internal class RangeConverter : JsonConverter + internal class RangeConverter : JsonConverter { - public override bool CanConvert(Type objectType) + public override bool CanConvert(Type typeToConvert) { - throw new NotImplementedException(); + return typeToConvert == typeof(IntRange); } - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + public override IntRange? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return reader.Value != null ? ParseIntRange(reader.Value.ToString()) : null; + if (reader.TokenType == JsonTokenType.String) + { + string? value = reader.GetString(); + return value != null ? ParseIntRange(value) : null; + } + throw new JsonException("Expected string value for IntRange."); } - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, IntRange value, JsonSerializerOptions options) { - if (value == null) return; - - var val = (IntRange)value; - writer.WriteValue(val.ToPostgresString()); + writer.WriteStringValue(value.ToPostgresString()); } public static IntRange ParseIntRange(string value) diff --git a/Postgrest/Helpers.cs b/Postgrest/Helpers.cs index 0f4f2f8..7ca1cd7 100644 --- a/Postgrest/Helpers.cs +++ b/Postgrest/Helpers.cs @@ -6,8 +6,8 @@ using System.Threading; using System.Threading.Tasks; using System.Web; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using Supabase.Core; using Supabase.Core.Extensions; using Supabase.Postgrest.Exceptions; @@ -18,7 +18,6 @@ namespace Supabase.Postgrest { - internal static class Helpers { private static readonly HttpClient Client = new HttpClient(); @@ -26,7 +25,7 @@ internal static class Helpers private static readonly Guid AppSession = Guid.NewGuid(); /// - /// Helper to make a request using the defined parameters to an API Endpoint and coerce into a model. + /// Helper to make a request using the defined parameters to an API Endpoint and coerce into a model. /// /// /// @@ -34,15 +33,15 @@ internal static class Helpers /// /// /// - /// + /// /// /// /// - public static async Task> MakeRequest(ClientOptions clientOptions, HttpMethod method, string url, JsonSerializerSettings serializerSettings, object? data = null, + public static async Task> MakeRequest(ClientOptions clientOptions, HttpMethod method, string url, JsonSerializerOptions serializerOptions, object? data = null, Dictionary? headers = null, Func>? getHeaders = null, CancellationToken cancellationToken = default) where T : BaseModel, new() { - var baseResponse = await MakeRequest(clientOptions, method, url, serializerSettings, data, headers, cancellationToken); - return new ModeledResponse(baseResponse, serializerSettings, getHeaders); + var baseResponse = await MakeRequest(clientOptions, method, url, serializerOptions, data, headers, cancellationToken); + return new ModeledResponse(baseResponse, serializerOptions, getHeaders); } /// @@ -53,10 +52,10 @@ public static async Task> MakeRequest(ClientOptions client /// /// /// - /// + /// /// /// - public static async Task MakeRequest(ClientOptions clientOptions, HttpMethod method, string url, JsonSerializerSettings serializerSettings, object? data = null, + public static async Task MakeRequest(ClientOptions clientOptions, HttpMethod method, string url, JsonSerializerOptions serializerOptions, object? data = null, Dictionary? headers = null, CancellationToken cancellationToken = default) { var builder = new UriBuilder(url); @@ -78,9 +77,9 @@ public static async Task MakeRequest(ClientOptions clientOptions, if (data != null && method != HttpMethod.Get) { - var stringContent = JsonConvert.SerializeObject(data, serializerSettings); + var stringContent = JsonSerializer.Serialize(data, serializerOptions); - if (!string.IsNullOrWhiteSpace(stringContent) && JToken.Parse(stringContent).HasValues) + if (!string.IsNullOrWhiteSpace(stringContent) && JsonDocument.Parse(stringContent).RootElement.ValueKind != JsonValueKind.Null) { requestMessage.Content = new StringContent(stringContent, Encoding.UTF8, "application/json"); } diff --git a/Postgrest/Hooks.cs b/Postgrest/Hooks.cs index b1782b2..d703709 100644 --- a/Postgrest/Hooks.cs +++ b/Postgrest/Hooks.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using Newtonsoft.Json; +using System.Text.Json; namespace Supabase.Postgrest { @@ -10,7 +10,7 @@ namespace Supabase.Postgrest /// public delegate void OnRequestPreparedEventHandler(object sender, ClientOptions clientOptions, HttpMethod method, string url, - JsonSerializerSettings serializerSettings, object? data = null, + JsonSerializerOptions serializerOptions, object? data = null, Dictionary? headers = null); /// @@ -74,18 +74,18 @@ public void ClearRequestPreparedHandlers() /// /// /// - /// + /// /// /// public void NotifyOnRequestPreparedHandlers(object sender, ClientOptions clientOptions, HttpMethod method, string url, - JsonSerializerSettings serializerSettings, object? data = null, + JsonSerializerOptions serializerOptions, object? data = null, Dictionary? headers = null) { Debugger.Instance.Log(this, $"{nameof(NotifyOnRequestPreparedHandlers)} called for [{method}] to {url}"); foreach (var handler in _requestPreparedEventHandlers.ToList()) - handler.Invoke(sender, clientOptions, method, url, serializerSettings, data, headers); + handler.Invoke(sender, clientOptions, method, url, serializerOptions, data, headers); } } -} \ No newline at end of file +} diff --git a/Postgrest/Models/BaseModel.cs b/Postgrest/Models/BaseModel.cs index 1b35ddd..95ef1b7 100644 --- a/Postgrest/Models/BaseModel.cs +++ b/Postgrest/Models/BaseModel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest.Attributes; using Supabase.Postgrest.Exceptions; using Supabase.Postgrest.Responses; diff --git a/Postgrest/Models/CachedModel.cs b/Postgrest/Models/CachedModel.cs index ee5ff39..6580d39 100644 --- a/Postgrest/Models/CachedModel.cs +++ b/Postgrest/Models/CachedModel.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Supabase.Postgrest.Models { @@ -13,11 +13,11 @@ namespace Supabase.Postgrest.Models /// /// The stored Models /// - [JsonProperty("response")] public List? Models { get; set; } + [JsonPropertyName("response")] public List? Models { get; set; } /// /// Cache time in UTC. /// - [JsonProperty("cachedAt")] public DateTime CachedAt { get; set; } + [JsonPropertyName("cachedAt")] public DateTime CachedAt { get; set; } } -} \ No newline at end of file +} diff --git a/Postgrest/Postgrest.csproj b/Postgrest/Postgrest.csproj index cca92b3..680f08f 100644 --- a/Postgrest/Postgrest.csproj +++ b/Postgrest/Postgrest.csproj @@ -44,7 +44,7 @@ - + diff --git a/Postgrest/PostgrestContractResolver.cs b/Postgrest/PostgrestContractResolver.cs index fcdfd0f..2b67691 100644 --- a/Postgrest/PostgrestContractResolver.cs +++ b/Postgrest/PostgrestContractResolver.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest.Attributes; -using Supabase.Postgrest.Converters; namespace Supabase.Postgrest { @@ -13,7 +12,7 @@ namespace Supabase.Postgrest /// A custom resolver that handles mapping column names and property names as well /// as handling the conversion of Postgrest Ranges to a C# `Range`. /// - public class PostgrestContractResolver : DefaultContractResolver + public class PostgrestContractResolver : JsonConverter { private bool IsUpdate { get; set; } private bool IsInsert { get; set; } @@ -32,79 +31,168 @@ public void SetState(bool isInsert = false, bool isUpdate = false, bool isUpsert IsUpsert = isUpsert; } - /// - protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + public override bool CanConvert(Type typeToConvert) { - JsonProperty prop = base.CreateProperty(member, memberSerialization); + return true; // This converter can handle any type + } - // Handle non-primitive conversions from a Postgres type to C# - if (prop.PropertyType == typeof(IntRange)) - { - prop.Converter = new RangeConverter(); - } - else if (prop.PropertyType != null && (prop.PropertyType == typeof(DateTime) || - Nullable.GetUnderlyingType(prop.PropertyType) == typeof(DateTime))) - { - prop.Converter = new DateTimeConverter(); - } - else if (prop.PropertyType == typeof(List)) - { - prop.Converter = new IntArrayConverter(); - } - else if (prop.PropertyType != null && (prop.PropertyType == typeof(List) || - Nullable.GetUnderlyingType(prop.PropertyType) == - typeof(List))) + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) { - prop.Converter = new DateTimeConverter(); + throw new JsonException("JSON object expected."); } - // Dynamically set the name of the key we are serializing/deserializing from the model. - if (!member.CustomAttributes.Any()) + var instance = Activator.CreateInstance(typeToConvert); + var properties = typeToConvert.GetProperties(); + + while (reader.Read()) { - return prop; + if (reader.TokenType == JsonTokenType.EndObject) + { + return instance; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("Property name expected."); + } + + string propertyName = reader.GetString()!; + reader.Read(); + + var property = FindProperty(properties, propertyName); + if (property != null) + { + object? value = JsonSerializer.Deserialize(ref reader, property.PropertyType, options); + property.SetValue(instance, value); + } + else + { + reader.Skip(); + } } - var columnAttribute = member.GetCustomAttribute(); + throw new JsonException("JSON object not properly closed."); + } - if (columnAttribute != null) + private PropertyInfo? FindProperty(PropertyInfo[] properties, string jsonPropertyName) + { + foreach (var prop in properties) { - prop.PropertyName = columnAttribute.ColumnName; - prop.NullValueHandling = columnAttribute.NullValueHandling; - - if (IsInsert && columnAttribute.IgnoreOnInsert) - prop.Ignored = true; - - if (IsUpdate && columnAttribute.IgnoreOnUpdate) - prop.Ignored = true; - - if ((IsUpsert && columnAttribute.IgnoreOnUpdate) || (IsUpsert && columnAttribute.IgnoreOnInsert)) - prop.Ignored = true; - - return prop; + var columnAttribute = prop.GetCustomAttribute(); + var referenceAttr = prop.GetCustomAttribute(); + var primaryKeyAttribute = prop.GetCustomAttribute(); + + if (columnAttribute != null && columnAttribute.ColumnName == jsonPropertyName) + { + return prop; + } + else if (referenceAttr != null) + { + string? refPropertyName = string.IsNullOrEmpty(referenceAttr.ForeignKey) + ? referenceAttr.TableName + : referenceAttr.ColumnName; + if (refPropertyName == jsonPropertyName) + { + return prop; + } + } + else if (primaryKeyAttribute != null && primaryKeyAttribute.ColumnName == jsonPropertyName) + { + return prop; + } + else if (prop.Name == jsonPropertyName) + { + return prop; + } } - var referenceAttr = member.GetCustomAttribute(); + return null; + } - if (referenceAttr != null) + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + if (value == null) { - // If a foreign key is not specified, PostgREST will return JSON that uses the table's name as the key. - prop.PropertyName = string.IsNullOrEmpty(referenceAttr.ForeignKey) - ? referenceAttr.TableName - : referenceAttr.ColumnName; + writer.WriteNullValue(); + return; + } - if (IsInsert || IsUpdate) - prop.Ignored = true; + writer.WriteStartObject(); - return prop; - } + var properties = value.GetType().GetProperties(); + var customOptions = new JsonSerializerOptions(options); + customOptions.Converters.Remove(this); - var primaryKeyAttribute = member.GetCustomAttribute(); - if (primaryKeyAttribute == null) - return prop; + foreach (var prop in properties) + { + var columnAttribute = prop.GetCustomAttribute(); + var referenceAttr = prop.GetCustomAttribute(); + var primaryKeyAttribute = prop.GetCustomAttribute(); + var ignoreAttribute = prop.GetCustomAttribute(); + + string? propertyName = prop.Name; + bool shouldSerialize = ignoreAttribute == null; + + if (columnAttribute != null) + { + propertyName = columnAttribute.ColumnName; + if ((IsInsert && columnAttribute.IgnoreOnInsert) || + (IsUpdate && columnAttribute.IgnoreOnUpdate) || + (IsUpsert && (columnAttribute.IgnoreOnUpdate || columnAttribute.IgnoreOnInsert))) + { + shouldSerialize = false; + } + } + else if (referenceAttr != null) + { + propertyName = string.IsNullOrEmpty(referenceAttr.ForeignKey) + ? referenceAttr.TableName + : referenceAttr.ColumnName; + if (IsInsert || IsUpdate) + { + shouldSerialize = false; + } + } + else if (primaryKeyAttribute != null) + { + propertyName = primaryKeyAttribute.ColumnName; + shouldSerialize = primaryKeyAttribute.ShouldInsert || (IsUpsert && value != null); + } + + if (shouldSerialize) + { + object? propValue = null; + try + { + propValue = prop.GetValue(value); + } + catch (TargetParameterCountException) + { + // Skip properties that require parameters + continue; + } + catch (Exception ex) + { + // Log or handle other exceptions as needed + Console.WriteLine($"Error getting value for property {prop.Name}: {ex.Message}"); + continue; + } + + if (propValue != null && propertyName != null) + { + // Check if the property is not the GetPrimaryKey method + if (prop.Name != "GetPrimaryKey" && !prop.Name.EndsWith("PrimaryKeyInternal")) + { + writer.WritePropertyName(propertyName); + JsonSerializer.Serialize(writer, propValue, prop.PropertyType, customOptions); + } + } + } + } - prop.PropertyName = primaryKeyAttribute.ColumnName; - prop.ShouldSerialize = instance => primaryKeyAttribute.ShouldInsert || (IsUpsert && instance != null); - return prop; + writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/Postgrest/QueryFilter.cs b/Postgrest/QueryFilter.cs index 6f21c55..0618ad9 100644 --- a/Postgrest/QueryFilter.cs +++ b/Postgrest/QueryFilter.cs @@ -4,7 +4,7 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest.Exceptions; using Supabase.Postgrest.Interfaces; using Supabase.Postgrest.Linq; @@ -83,7 +83,7 @@ public QueryFilter(string property, Operator op, object? criteria) criteria = dateTime.ToString("o", CultureInfo.InvariantCulture); if (criteria is DateTimeOffset dateTimeOffset) criteria = dateTimeOffset.ToString("o", CultureInfo.InvariantCulture); - + switch (op) { case Operator.And: @@ -116,12 +116,12 @@ public QueryFilter(string property, Operator op, object? criteria) { throw new PostgrestException( "List or Dictionary must be used supplied as criteria with filters that accept an array of arguments.") - { Reason = FailureHint.Reason.InvalidArgument }; + { Reason = FailureHint.Reason.InvalidArgument }; } break; default: throw new PostgrestException("Advanced filters require a constructor with more specific arguments") - { Reason = FailureHint.Reason.InvalidArgument }; + { Reason = FailureHint.Reason.InvalidArgument }; } } @@ -145,7 +145,7 @@ public QueryFilter(string property, Operator op, FullTextSearchConfig fullTextSe break; default: throw new PostgrestException("Constructor must be called with a full text search operator") - { Reason = FailureHint.Reason.InvalidArgument }; + { Reason = FailureHint.Reason.InvalidArgument }; } } @@ -195,7 +195,7 @@ public QueryFilter(Operator op, List filters) break; default: throw new PostgrestException("Constructor can only be used with `or` or `and` filters") - { Reason = FailureHint.Reason.InvalidArgument }; + { Reason = FailureHint.Reason.InvalidArgument }; } } @@ -214,7 +214,7 @@ public QueryFilter(Operator op, IPostgrestQueryFilter filter) break; default: throw new PostgrestException("Constructor can only be used with `not` filter") - { Reason = FailureHint.Reason.InvalidArgument }; + { Reason = FailureHint.Reason.InvalidArgument }; } } } @@ -228,13 +228,13 @@ public class FullTextSearchConfig /// /// Query Text /// - [JsonProperty("queryText")] + [JsonPropertyName("queryText")] public string QueryText { get; private set; } /// /// Defaults to english /// - [JsonProperty("config")] + [JsonPropertyName("config")] public string Config { get; private set; } = "english"; /// @@ -250,4 +250,4 @@ public FullTextSearchConfig(string queryText, string? config) Config = config!; } } -} \ No newline at end of file +} diff --git a/Postgrest/Responses/BaseResponse.cs b/Postgrest/Responses/BaseResponse.cs index ceeeae6..546ca7f 100644 --- a/Postgrest/Responses/BaseResponse.cs +++ b/Postgrest/Responses/BaseResponse.cs @@ -1,5 +1,5 @@ using System.Net.Http; -using Newtonsoft.Json; +using System.Text.Json.Serialization; #pragma warning disable CS1591 namespace Supabase.Postgrest.Responses { diff --git a/Postgrest/Responses/ModeledResponse.cs b/Postgrest/Responses/ModeledResponse.cs index 7a79742..7f22610 100644 --- a/Postgrest/Responses/ModeledResponse.cs +++ b/Postgrest/Responses/ModeledResponse.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest.Extensions; using Supabase.Postgrest.Models; namespace Supabase.Postgrest.Responses { - /// /// A representation of a successful Postgrest response that transforms the string response into a C# Modelled response. /// @@ -24,55 +23,57 @@ namespace Supabase.Postgrest.Responses /// A list of models in the response. /// public List Models { get; } = new(); - + /// - public ModeledResponse(BaseResponse baseResponse, JsonSerializerSettings serializerSettings, Func>? getHeaders = null, bool shouldParse = true) : base(baseResponse.ClientOptions, baseResponse.ResponseMessage, baseResponse.Content) + public ModeledResponse(BaseResponse baseResponse, JsonSerializerOptions serializerOptions, Func>? getHeaders = null, bool shouldParse = true) : base(baseResponse.ClientOptions, baseResponse.ResponseMessage, baseResponse.Content) { Content = baseResponse.Content; ResponseMessage = baseResponse.ResponseMessage; if (!shouldParse || string.IsNullOrEmpty(Content)) return; - var token = JToken.Parse(Content!); + var jsonDocument = JsonDocument.Parse(Content!); - switch (token) + switch (jsonDocument.RootElement.ValueKind) { // A List of models has been returned - case JArray: { - var deserialized = JsonConvert.DeserializeObject>(Content!, serializerSettings); + case JsonValueKind.Array: + { + var deserialized = JsonSerializer.Deserialize>(Content!, serializerOptions); - if (deserialized != null) - Models = deserialized; + if (deserialized != null) + Models = deserialized; - foreach (var model in Models) - { - model.BaseUrl = baseResponse.ResponseMessage!.RequestMessage.RequestUri.GetInstanceUrl().Replace(model.TableName, "").TrimEnd('/'); - model.RequestClientOptions = ClientOptions; - model.GetHeaders = getHeaders; - } + foreach (var model in Models) + { + model.BaseUrl = baseResponse.ResponseMessage!.RequestMessage.RequestUri.GetInstanceUrl().Replace(model.TableName, "").TrimEnd('/'); + model.RequestClientOptions = ClientOptions; + model.GetHeaders = getHeaders; + } - break; - } + break; + } // A single model has been returned - case JObject: { - Models.Clear(); + case JsonValueKind.Object: + { + Models.Clear(); - var obj = JsonConvert.DeserializeObject(Content!, serializerSettings); + var obj = JsonSerializer.Deserialize(Content!, serializerOptions); - if (obj != null) - { - obj.BaseUrl = baseResponse.ResponseMessage!.RequestMessage.RequestUri.GetInstanceUrl().Replace(obj.TableName, "").TrimEnd('/'); - obj.RequestClientOptions = ClientOptions; - obj.GetHeaders = getHeaders; + if (obj != null) + { + obj.BaseUrl = baseResponse.ResponseMessage!.RequestMessage.RequestUri.GetInstanceUrl().Replace(obj.TableName, "").TrimEnd('/'); + obj.RequestClientOptions = ClientOptions; + obj.GetHeaders = getHeaders; - Models.Add(obj); - } + Models.Add(obj); + } - break; - } + break; + } } - Debugger.Instance.Log(this, $"Response: [{baseResponse.ResponseMessage?.StatusCode}]\n" + $"Parsed Models <{typeof(T).Name}>:\n\t{JsonConvert.SerializeObject(Models)}\n"); + Debugger.Instance.Log(this, $"Response: [{baseResponse.ResponseMessage?.StatusCode}]\n" + $"Parsed Models <{typeof(T).Name}>:\n\t{JsonSerializer.Serialize(Models, serializerOptions)}\n"); } } } diff --git a/Postgrest/Table.cs b/Postgrest/Table.cs index 58b3f8c..1ce7a3d 100644 --- a/Postgrest/Table.cs +++ b/Postgrest/Table.cs @@ -10,7 +10,8 @@ using System.Threading; using System.Threading.Tasks; using System.Web; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest.Extensions; using Supabase.Core.Attributes; using Supabase.Core.Extensions; @@ -26,7 +27,7 @@ namespace Supabase.Postgrest { /// /// Class created from a model derived from `BaseModel` that can generate query requests to a Postgrest Endpoint. - /// + /// /// Representative of a `USE $TABLE` command. /// /// Model derived from `BaseModel`. @@ -42,7 +43,7 @@ namespace Supabase.Postgrest public Func>? GetHeaders { get; set; } private readonly ClientOptions _options; - private readonly JsonSerializerSettings _serializerSettings; + private readonly JsonSerializerOptions _serializerOptions; private HttpMethod _method = HttpMethod.Get; @@ -75,14 +76,14 @@ namespace Supabase.Postgrest /// Typically called from the Client `new Client.Table<ModelType>` /// /// Api Endpoint (ex: "http://localhost:8000"), no trailing slash required. - /// + /// /// Optional client configuration. - public Table(string baseUrl, JsonSerializerSettings serializerSettings, ClientOptions? options = null) + public Table(string baseUrl, JsonSerializerOptions serializerOptions, ClientOptions? options = null) { BaseUrl = baseUrl; _options = options ?? new ClientOptions(); - _serializerSettings = serializerSettings; + _serializerOptions = serializerOptions; foreach (var property in typeof(TModel).GetProperties()) { @@ -134,7 +135,7 @@ public IPostgrestTable Filter(string columnName, Operator op default: throw new PostgrestException( "NOT filters must use the `Equals`, `Is`, `Not` or `NotEqual` operators") - { Reason = FailureHint.Reason.InvalidArgument }; + { Reason = FailureHint.Reason.InvalidArgument }; } return this; @@ -518,8 +519,8 @@ public Task> Update(QueryOptions? options = null, throw new ArgumentException("No data has been set to update, was `Set` called?"); _method = new HttpMethod("PATCH"); - - var request = Send(_method, _setData, options.ToHeaders(), cancellationToken, isUpdate: true); + var data = (TModel)Convert.ChangeType(_setData, typeof(TModel)); + var request = Send(_method, data, options.ToHeaders(), cancellationToken, isUpdate: true); Clear(); @@ -552,7 +553,7 @@ public Task Delete(QueryOptions? options = null, CancellationToken cancellationT _method = HttpMethod.Delete; - var request = Send(_method, null, options.ToHeaders(), cancellationToken); + var request = Send(_method, null, options.ToHeaders(), cancellationToken); Clear(); @@ -588,7 +589,7 @@ public async Task Count(CountType type, CancellationToken cancellationToken { "Prefer", $"count={attr?.Mapping}" } }; - var request = Send(_method, null, headers, cancellationToken); + var request = Send(_method, null, headers, cancellationToken); Clear(); var response = await request; @@ -673,7 +674,7 @@ public string GenerateUrl() var selector = !string.IsNullOrEmpty(orderer.ForeignTable) ? orderer.ForeignTable + "(" + orderer.Column + ")" : orderer.Column; - + order.Append($"{selector}.{orderingAttr.Mapping}.{nullPosAttr.Mapping}"); } @@ -732,32 +733,30 @@ public string GenerateUrl() } /// - /// Transforms an object into a string mapped list/dictionary using `JsonSerializerSettings`. + /// Transforms an object into a string mapped list/dictionary using `JsonSerializerOptions`. /// /// /// /// /// /// - private object? PrepareRequestData(object? data, bool isInsert = false, bool isUpdate = false, + private object? PrepareRequestData(T? data, bool isInsert = false, bool isUpdate = false, bool isUpsert = false) { if (data == null) return new Dictionary(); + var postgrestContractResolver = new PostgrestContractResolver(); + postgrestContractResolver.SetState(isInsert, isUpdate, isUpsert); + var options = new JsonSerializerOptions(_serializerOptions); + options.Converters.Add(postgrestContractResolver); - // Specified in constructor; - var resolver = (PostgrestContractResolver)_serializerSettings.ContractResolver!; - - resolver.SetState(isInsert, isUpdate, isUpsert); - var serialized = JsonConvert.SerializeObject(data, _serializerSettings); - - resolver.SetState(); + var serialized = JsonSerializer.Serialize(data, options); // Check if data is a Collection for the Insert Bulk case if (data is ICollection) - return JsonConvert.DeserializeObject>(serialized, _serializerSettings); - - return JsonConvert.DeserializeObject>(serialized, _serializerSettings); + return JsonSerializer.Deserialize>(serialized, options); + // TODO: resolve options causing test failures + return JsonSerializer.Deserialize>(serialized); } /// @@ -823,7 +822,7 @@ internal KeyValuePair PrepareFilter(IPostgrestQueryFilter filter if (filter is { Criteria: IDictionary inDictCriteria, Property: not null }) { return new KeyValuePair(filter.Property, - $"{asAttribute.Mapping}.{JsonConvert.SerializeObject(inDictCriteria)}"); + $"{asAttribute.Mapping}.{JsonSerializer.Serialize(inDictCriteria, _serializerOptions)}"); } break; @@ -833,16 +832,16 @@ internal KeyValuePair PrepareFilter(IPostgrestQueryFilter filter switch (filter.Criteria) { case IList listCriteria when filter.Property != null: - { - foreach (var item in listCriteria) - strBuilder.Append($"{item},"); + { + foreach (var item in listCriteria) + strBuilder.Append($"{item},"); - return new KeyValuePair(filter.Property, - $"{asAttribute.Mapping}.{{{strBuilder.ToString().Trim(',')}}}"); - } + return new KeyValuePair(filter.Property, + $"{asAttribute.Mapping}.{{{strBuilder.ToString().Trim(',')}}}"); + } case IDictionary dictCriteria when filter.Property != null: return new KeyValuePair(filter.Property, - $"{asAttribute.Mapping}.{JsonConvert.SerializeObject(dictCriteria)}"); + $"{asAttribute.Mapping}.{JsonSerializer.Serialize(dictCriteria, _serializerOptions)}"); case IntRange rangeCriteria when filter.Property != null: return new KeyValuePair(filter.Property, $"{asAttribute.Mapping}.{rangeCriteria.ToPostgresString()}"); @@ -919,8 +918,9 @@ private Task> PerformInsert(object data, QueryOptions? o if (!string.IsNullOrEmpty(options.OnConflict)) OnConflict(options.OnConflict!); + var castData = (TModel)Convert.ChangeType(data, typeof(TModel)); - var request = Send(_method, data, options.ToHeaders(), cancellationToken, isInsert: true, + var request = Send(_method, castData, options.ToHeaders(), cancellationToken, isInsert: true, isUpsert: options.Upsert); Clear(); @@ -928,33 +928,33 @@ private Task> PerformInsert(object data, QueryOptions? o return request; } - private Task Send(HttpMethod method, object? data, Dictionary? headers = null, - CancellationToken cancellationToken = default, bool isInsert = false, - bool isUpdate = false, bool isUpsert = false) - { - var requestHeaders = Helpers.PrepareRequestHeaders(method, headers, _options, _rangeFrom, _rangeTo); + // private Task Send(HttpMethod method, T? data, Dictionary? headers = null, + // CancellationToken cancellationToken = default, bool isInsert = false, + // bool isUpdate = false, bool isUpsert = false) + // { + // var requestHeaders = Helpers.PrepareRequestHeaders(method, headers, _options, _rangeFrom, _rangeTo); - if (GetHeaders != null) - { - requestHeaders = GetHeaders().MergeLeft(requestHeaders); - } + // if (GetHeaders != null) + // { + // requestHeaders = GetHeaders().MergeLeft(requestHeaders); + // } - var url = GenerateUrl(); - var preparedData = PrepareRequestData(data, isInsert, isUpdate, isUpsert); + // var url = GenerateUrl(); + // var preparedData = PrepareRequestData(data, isInsert, isUpdate, isUpsert); - Hooks.Instance.NotifyOnRequestPreparedHandlers(this, _options, method, url, _serializerSettings, - preparedData, requestHeaders); + // Hooks.Instance.NotifyOnRequestPreparedHandlers(this, _options, method, url, _serializerOptions, + // preparedData, requestHeaders); - Debugger.Instance.Log(this, - $"Request [{method}] at {DateTime.Now.ToLocalTime()}\n" + - $"Headers:\n\t{JsonConvert.SerializeObject(requestHeaders)}\n" + - $"Data:\n\t{JsonConvert.SerializeObject(preparedData)}"); + // Debugger.Instance.Log(this, + // $"Request [{method}] at {DateTime.Now.ToLocalTime()}\n" + + // $"Headers:\n\t{JsonSerializer.Serialize(requestHeaders, _serializerOptions)}\n" + + // $"Data:\n\t{JsonSerializer.Serialize(preparedData, _serializerOptions)}"); - return Helpers.MakeRequest(_options, method, url, _serializerSettings, preparedData, requestHeaders, - cancellationToken); - } + // return Helpers.MakeRequest(_options, method, url, _serializerOptions, preparedData, requestHeaders, + // cancellationToken); + // } - private Task> Send(HttpMethod method, object? data, + private Task> Send(HttpMethod method, TU? data, Dictionary? headers = null, CancellationToken cancellationToken = default, bool isInsert = false, bool isUpdate = false, bool isUpsert = false) where TU : BaseModel, new() @@ -965,17 +965,17 @@ private Task> Send(HttpMethod method, object? data, requestHeaders = GetHeaders().MergeLeft(requestHeaders); var url = GenerateUrl(); - var preparedData = PrepareRequestData(data, isInsert, isUpdate, isUpsert); - - Hooks.Instance.NotifyOnRequestPreparedHandlers(this, _options, method, url, _serializerSettings, + var preparedData = PrepareRequestData(data, isInsert, isUpdate, isUpsert); + // var preparedData = data; + Hooks.Instance.NotifyOnRequestPreparedHandlers(this, _options, method, url, _serializerOptions, preparedData, requestHeaders); Debugger.Instance.Log(this, $"Request [{method}] at {DateTime.Now.ToLocalTime()}\n" + - $"Headers:\n\t{JsonConvert.SerializeObject(requestHeaders)}\n" + - $"Data:\n\t{JsonConvert.SerializeObject(preparedData)}"); + $"Headers:\n\t{JsonSerializer.Serialize(requestHeaders, _serializerOptions)}\n" + + $"Data:\n\t{JsonSerializer.Serialize(preparedData, _serializerOptions)}"); - return Helpers.MakeRequest(_options, method, url, _serializerSettings, preparedData, requestHeaders, + return Helpers.MakeRequest(_options, method, url, _serializerOptions, preparedData, requestHeaders, GetHeaders, cancellationToken); } @@ -992,4 +992,4 @@ private static string FindTableName(object? obj = null) return type.Name; } } -} \ No newline at end of file +} diff --git a/Postgrest/TableWithCache.cs b/Postgrest/TableWithCache.cs index 284f741..41885cc 100644 --- a/Postgrest/TableWithCache.cs +++ b/Postgrest/TableWithCache.cs @@ -1,7 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; +using System.Text.Json; using Supabase.Postgrest.Interfaces; using Supabase.Postgrest.Models; using Supabase.Postgrest.Requests; @@ -22,14 +22,14 @@ namespace Supabase.Postgrest /// public TableWithCache(string baseUrl, IPostgrestCacheProvider cacheProvider, - JsonSerializerSettings serializerSettings, ClientOptions? options = null) - : base(baseUrl, serializerSettings, options) + JsonSerializerOptions serializerOptions, ClientOptions? options = null) + : base(baseUrl, serializerOptions, options) { CacheProvider = cacheProvider; } /// - /// + /// /// /// /// @@ -45,4 +45,4 @@ public TableWithCache(string baseUrl, IPostgrestCacheProvider cacheProvider, return cacheModel; } } -} \ No newline at end of file +} diff --git a/PostgrestTests/ClientTests.cs b/PostgrestTests/ClientTests.cs index 47bd4c6..283a353 100644 --- a/PostgrestTests/ClientTests.cs +++ b/PostgrestTests/ClientTests.cs @@ -13,6 +13,7 @@ using Supabase.Postgrest.Responses; using PostgrestTests.Models; using static Supabase.Postgrest.Constants; +using System.Text.Json; namespace PostgrestTests { @@ -1051,7 +1052,10 @@ public async Task TestSupportIntArraysAsLists() .Insert( new User { - Username = "WALRUS", Status = "ONLINE", Catchphrase = "I'm a walrus", FavoriteNumbers = numbers, + Username = "WALRUS", + Status = "ONLINE", + Catchphrase = "I'm a walrus", + FavoriteNumbers = numbers, AgeRange = new IntRange(15, 25) }, new QueryOptions { Upsert = true }); @@ -1061,17 +1065,17 @@ public async Task TestSupportIntArraysAsLists() [TestMethod("stored procedure")] public async Task TestStoredProcedure() { - //Arrange + //Arrange var client = new Client(BaseUrl); - //Act + //Act var parameters = new Dictionary { { "name_param", "supabot" } }; var response = await client.Rpc("get_status", parameters); - //Assert + //Assert Assert.AreEqual(true, response.ResponseMessage?.IsSuccessStatusCode); Assert.AreEqual(true, response.Content?.Contains("OFFLINE")); } @@ -1079,10 +1083,10 @@ public async Task TestStoredProcedure() [TestMethod("stored procedure with row param")] public async Task TestStoredProcedureWithRowParam() { - //Arrange + //Arrange var client = new Client(BaseUrl); - //Act + //Act var parameters = new Dictionary { { @@ -1095,7 +1099,7 @@ public async Task TestStoredProcedureWithRowParam() }; var response = await client.Rpc("get_data", parameters); - //Assert + //Assert Assert.AreEqual(true, response.ResponseMessage?.IsSuccessStatusCode); Assert.AreEqual("null", response.Content); } @@ -1111,10 +1115,10 @@ public async Task TestSwitchSchema() }; var client = new Client(BaseUrl, options); - //Act + //Act var response = await client.Table().Filter(x => x.Username!, Operator.Equals, "leroyjenkins").Get(); - //Assert + //Assert Assert.AreEqual(1, response.Models.Count); Assert.AreEqual("leroyjenkins", response.Models.FirstOrDefault()?.Username); } @@ -1195,4 +1199,4 @@ public async Task TestOnRequestPreparedEvent() Assert.IsTrue(timer1.ElapsedTicks < timer2.ElapsedTicks); } } -} \ No newline at end of file +} diff --git a/PostgrestTests/LinqTests.cs b/PostgrestTests/LinqTests.cs index d65eba8..c8e8f1d 100644 --- a/PostgrestTests/LinqTests.cs +++ b/PostgrestTests/LinqTests.cs @@ -407,4 +407,4 @@ public async Task TestLinqQueryFilter() } } } -} \ No newline at end of file +} diff --git a/PostgrestTests/Models/KitchenSink.cs b/PostgrestTests/Models/KitchenSink.cs index e3031bb..805ddd4 100644 --- a/PostgrestTests/Models/KitchenSink.cs +++ b/PostgrestTests/Models/KitchenSink.cs @@ -1,7 +1,7 @@ #nullable enable using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using Supabase.Postgrest; using Supabase.Postgrest.Attributes; using Supabase.Postgrest.Models; @@ -27,18 +27,22 @@ public class KitchenSink : BaseModel [Column("datetime_value")] public DateTime? DateTimeValue { get; set; } - [Column("datetime_value_1", NullValueHandling = NullValueHandling.Ignore)] + [Column("datetime_value_1")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DateTime? DateTimeValue1 { get; set; } - [Column("datetime_pos_infinite_value", NullValueHandling = NullValueHandling.Ignore)] + [Column("datetime_pos_infinite_value")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DateTime? DateTimePosInfinity { get; set; } - [Column("datetime_neg_infinite_value", NullValueHandling = NullValueHandling.Ignore)] + [Column("datetime_neg_infinite_value")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public DateTime? DateTimeNegInfinity { get; set; } [Column("list_of_strings")] public List? ListOfStrings { get; set; } - [Column("list_of_datetimes", NullValueHandling = NullValueHandling.Ignore)] + [Column("list_of_datetimes")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public List? ListOfDateTimes { get; set; } [Column("list_of_ints")] public List? ListOfInts { get; set; } @@ -49,4 +53,4 @@ public class KitchenSink : BaseModel [Column("uuidv4")] public Guid? Uuidv4 { get; set; } } -} \ No newline at end of file +} diff --git a/PostgrestTests/Models/User.cs b/PostgrestTests/Models/User.cs index e61a1c6..58ca509 100644 --- a/PostgrestTests/Models/User.cs +++ b/PostgrestTests/Models/User.cs @@ -17,7 +17,7 @@ public class User : BaseModel [Column("favorite_numbers")] public List? FavoriteNumbers { get; set; } - + [Column("favorite_name")] public string? FavoriteName { get; set; }