Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Migrating from Newtonsoft.Json to System.Text.Json #100

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Postgrest/Attributes/ColumnAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Supabase.Postgrest.Attributes
{

Expand All @@ -26,7 +26,7 @@ public class ColumnAttribute : Attribute
/// <summary>
/// Specifies what should be serialized in the event this column's value is NULL
/// </summary>
public NullValueHandling NullValueHandling { get; set; }
public JsonIgnoreCondition NullValueHandling { get; set; }

/// <summary>
/// If the performed query is an Insert or Upsert, should this value be ignored?
Expand All @@ -39,7 +39,7 @@ public class ColumnAttribute : Attribute
public bool IgnoreOnUpdate { get; }

/// <inheritdoc />
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;
Expand Down
46 changes: 22 additions & 24 deletions Postgrest/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,39 @@
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
{
/// <inheritdoc />
public class Client : IPostgrestClient
{
/// <summary>
/// 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.
/// </summary>
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 DateTimeConverter(),
new RangeConverter()
}
};
}
Expand Down Expand Up @@ -70,7 +70,7 @@ public void RemoveDebugHandler(IPostgrestDebugger.DebugEventHandler handler) =>

/// <summary>
/// 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.
/// </summary>
public Func<Dictionary<string, string>>? GetHeaders { get; set; }
Expand All @@ -87,29 +87,27 @@ public Client(string baseUrl, ClientOptions? options = null)
Options = options ?? new ClientOptions();
}


/// <inheritdoc />
public IPostgrestTable<T> Table<T>() where T : BaseModel, new() =>
new Table<T>(BaseUrl, SerializerSettings(Options), Options)
new Table<T>(BaseUrl, SerializerOptions(Options), Options)
{
GetHeaders = GetHeaders
};

/// <inheritdoc />
public IPostgrestTableWithCache<T> Table<T>(IPostgrestCacheProvider cacheProvider)
where T : BaseModel, new() =>
new TableWithCache<T>(BaseUrl, cacheProvider, SerializerSettings(Options), Options)
new TableWithCache<T>(BaseUrl, cacheProvider, SerializerOptions(Options), Options)
{
GetHeaders = GetHeaders
};


/// <inheritdoc />
public async Task<TModeledResponse?> Rpc<TModeledResponse>(string procedureName, object? parameters = null)
{
var response = await Rpc(procedureName, parameters);

return string.IsNullOrEmpty(response.Content) ? default : JsonConvert.DeserializeObject<TModeledResponse>(response.Content!);
return string.IsNullOrEmpty(response.Content) ? default : JsonSerializer.Deserialize<TModeledResponse>(response.Content!, SerializerOptions(Options));
}

/// <inheritdoc />
Expand All @@ -120,13 +118,13 @@ public Task<BaseResponse> Rpc(string procedureName, object? parameters = null)

var canonicalUri = builder.Uri.ToString();

var serializerSettings = SerializerSettings(Options);
var serializerOptions = SerializerOptions(Options);

// Prepare parameters
Dictionary<string, object>? data = null;
if (parameters != null)
data = JsonConvert.DeserializeObject<Dictionary<string, object>>(
JsonConvert.SerializeObject(parameters, serializerSettings));
data = JsonSerializer.Deserialize<Dictionary<string, object>>(
JsonSerializer.Serialize(parameters, serializerOptions));

// Prepare headers
var headers = Helpers.PrepareRequestHeaders(HttpMethod.Post,
Expand All @@ -137,8 +135,8 @@ public Task<BaseResponse> 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;
}
}
}
}
120 changes: 82 additions & 38 deletions Postgrest/Converters/DateTimeConverter.cs
Original file line number Diff line number Diff line change
@@ -1,69 +1,75 @@
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
{

/// <inheritdoc />
public class DateTimeConverter : JsonConverter
public class DateTimeConverter : JsonConverter<object>
{
/// <inheritdoc />
public override bool CanConvert(Type objectType)
public override bool CanConvert(Type typeToConvert)
{
throw new NotImplementedException();
return typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?) ||
typeToConvert == typeof(List<DateTime>) || typeToConvert == typeof(List<DateTime?>);
}

/// <inheritdoc />
public override bool CanWrite => false;

/// <inheritdoc />
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.Null)
{
var str = reader.Value.ToString();

var infinity = ParseInfinity(str);
return null;
}

if (infinity != null)
if (reader.TokenType == JsonTokenType.String)
{
var str = reader.GetString();
if (string.IsNullOrEmpty(str))
{
return (DateTime)infinity;
return null;
}

var date = DateTime.Parse(str);
return date;
var infinity = ParseInfinity(str);

Check warning on line 35 in Postgrest/Converters/DateTimeConverter.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'input' in 'DateTime? DateTimeConverter.ParseInfinity(string input)'.

Check warning on line 35 in Postgrest/Converters/DateTimeConverter.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'input' in 'DateTime? DateTimeConverter.ParseInfinity(string input)'.
return infinity ?? DateTime.Parse(str);
}

var result = new List<DateTime>();

try
if (reader.TokenType == JsonTokenType.StartArray)
{
var jo = JArray.Load(reader);
var result = new List<DateTime?>();

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.Null)
{
result.Add(null);
}
else if (reader.TokenType == JsonTokenType.String)
{
var inner = reader.GetString();
if (string.IsNullOrEmpty(inner))
{
result.Add(null);
}
else
{
var infinity = ParseInfinity(inner);

Check warning on line 63 in Postgrest/Converters/DateTimeConverter.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'input' in 'DateTime? DateTimeConverter.ParseInfinity(string input)'.

Check warning on line 63 in Postgrest/Converters/DateTimeConverter.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Possible null reference argument for parameter 'input' in 'DateTime? DateTimeConverter.ParseInfinity(string input)'.
result.Add(infinity ?? DateTime.Parse(inner));
}
}
}
}
catch (JsonReaderException)
{
return null;
}

return result;
}

return result;
return null;
}

private static DateTime? ParseInfinity(string input)
Expand All @@ -77,9 +83,47 @@
}

/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
throw new NotImplementedException();
if (value is DateTime dateTime)
{
if (dateTime == DateTime.MinValue)
{
writer.WriteStringValue("-infinity");
}
else if (dateTime == DateTime.MaxValue)
{
writer.WriteStringValue("infinity");
}
else
{
writer.WriteStringValue(dateTime.ToString("O"));
}
}
else if (value is List<DateTime> dateTimeList)
{
writer.WriteStartArray();
foreach (var dt in dateTimeList)
{
if (dt == DateTime.MinValue)
{
writer.WriteStringValue("-infinity");
}
else if (dt == DateTime.MaxValue)
{
writer.WriteStringValue("infinity");
}
else
{
writer.WriteStringValue(dt.ToString("O"));
}
}
writer.WriteEndArray();
}
else
{
throw new JsonException("Unsupported value type for DateTimeConverter.");
}
}
}
}
47 changes: 33 additions & 14 deletions Postgrest/Converters/IntConverter.cs
Original file line number Diff line number Diff line change
@@ -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
{

/// <inheritdoc />
public class IntArrayConverter : JsonConverter
public class IntArrayConverter : JsonConverter<List<int>>
{
/// <inheritdoc />
public override bool CanConvert(Type objectType)
public override bool CanConvert(Type typeToConvert)
{
throw new NotImplementedException();
return typeToConvert == typeof(List<int>);
}

/// <inheritdoc />
public override bool CanRead => false;

/// <inheritdoc />
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
public override List<int>? 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<int>();

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.");
}

/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
public override void Write(Utf8JsonWriter writer, List<int> value, JsonSerializerOptions options)
{
if (value is List<int> list)
{
writer.WriteValue($"{{{string.Join(",", list)}}}");
}
writer.WriteStringValue($"{{{string.Join(",", value)}}}");
}
}
}
Loading
Loading