Skip to content

Commit

Permalink
feat(csharp): remove Newtonsoft JSON.NET (#2717)
Browse files Browse the repository at this point in the history
  • Loading branch information
morganleroi authored Feb 16, 2024
1 parent 0bafedc commit ee14887
Show file tree
Hide file tree
Showing 24 changed files with 354 additions and 628 deletions.
2 changes: 1 addition & 1 deletion .github/.cache_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.13
1.0.14
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Algolia.Search.Models.Common;

namespace Algolia.Search.Http;
Expand Down Expand Up @@ -54,7 +55,7 @@ private static bool HasEnumMemberAttrValue(object enumVal)
throw new ArgumentNullException(nameof(enumVal));
var enumType = enumVal.GetType();
var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<EnumMemberAttribute>().FirstOrDefault();
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<JsonPropertyNameAttribute>().FirstOrDefault();
return attr != null;
}

Expand All @@ -69,7 +70,7 @@ private static string GetEnumMemberAttrValue(object enumVal)
throw new ArgumentNullException(nameof(enumVal));
var enumType = enumVal.GetType();
var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<EnumMemberAttribute>().FirstOrDefault();
return attr?.Value;
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<JsonPropertyNameAttribute>().FirstOrDefault();
return attr?.Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Tasks;
using Algolia.Search.Exceptions;
using Algolia.Search.Models.Common;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;


namespace Algolia.Search.Serializer;

Expand All @@ -26,7 +28,7 @@ public string Serialize(object data)
{
if (data is not AbstractSchema schema)
{
return JsonConvert.SerializeObject(data, JsonConfig.AlgoliaJsonSerializerSettings);
return JsonSerializer.Serialize(data, JsonConfig.Options);
}

// the object to be serialized is a oneOf/anyOf schema
Expand All @@ -51,8 +53,12 @@ private async Task<object> Deserialize(Stream response, Type type)
{
using var reader = new StreamReader(response);
var readToEndAsync = await reader.ReadToEndAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject(readToEndAsync, type,
JsonConfig.AlgoliaJsonSerializerSettings);
if (string.IsNullOrEmpty(readToEndAsync))
{
return null;
}

return JsonSerializer.Deserialize(readToEndAsync, type, JsonConfig.Options);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Algolia.Search.Serializer;

/// <summary>
/// Factory to create a JsonConverter for enums
/// </summary>
public class JsonStringEnumConverterFactory : JsonConverterFactory
{
/// <summary>
/// Check if the type is an enum
/// </summary>
/// <param name="typeToConvert"></param>
/// <returns></returns>
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsEnum;
}

/// <summary>
/// Create a new converter
/// </summary>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var type = typeof(JsonStringEnumConverter<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(type)!;
}
}

/// <summary>
/// Custom JsonConverter to convert enum to string, using the JsonPropertyNameAttribute if present
/// </summary>
/// <typeparam name="TEnum"></typeparam>
public class JsonStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
{
private readonly Dictionary<TEnum, string> _enumToString = new();
private readonly Dictionary<string, TEnum> _stringToEnum = new();
private readonly Dictionary<int, TEnum> _numberToEnum = new();

/// <summary>
/// Constructor to create the converter
/// </summary>
public JsonStringEnumConverter()
{
var type = typeof(TEnum);
foreach (var value in Enum.GetValues(type))
{
var enumMember = type.GetMember(value.ToString())[0];
var attr = enumMember.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false)
.Cast<JsonPropertyNameAttribute>()
.FirstOrDefault();

var num = Convert.ToInt32(type.GetField("value__")?.GetValue(value));
if (attr?.Name != null)
{
_enumToString.Add((TEnum)value, attr.Name);
_stringToEnum.Add(attr.Name, (TEnum)value);
_numberToEnum.Add(num, (TEnum)value);
}
else
{
_enumToString.Add((TEnum)value, value.ToString());
_stringToEnum.Add(value.ToString(), (TEnum)value);
_numberToEnum.Add(num, (TEnum)value);
}
}
}


/// <summary>
/// Read the enum from the json
/// </summary>
/// <param name="reader"></param>
/// <param name="typeToConvert"></param>
/// <param name="options"></param>
/// <returns></returns>
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var type = reader.TokenType;
if (type == JsonTokenType.String)
{
var stringValue = reader.GetString();

if (stringValue != null && _stringToEnum.TryGetValue(stringValue, out var enumValue))
{
return enumValue;
}
}
else if (type == JsonTokenType.Number)
{
var numValue = reader.GetInt32();
_numberToEnum.TryGetValue(numValue, out var enumValue);
return enumValue;
}

return default;
}

/// <summary>
/// Write the enum to the json
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="options"></param>
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
var success = _enumToString.TryGetValue(value, out var stringValue);
if (success)
{
writer.WriteStringValue(stringValue);
}
else
{
writer.WriteNullValue();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Algolia.Search.Models.Search;

namespace Algolia.Search.Serializer;

Expand All @@ -10,16 +11,16 @@ internal static class JsonConfig
{
public const string JsonContentType = "application/json";

private static readonly DefaultContractResolver Resolver = new() { NamingStrategy = new CamelCaseNamingStrategy() };

public static readonly JsonSerializerSettings AlgoliaJsonSerializerSettings = new()
public static readonly JsonSerializerOptions Options = new()
{
Formatting = Formatting.None,
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = Resolver,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
DateParseHandling = DateParseHandling.DateTime,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
MissingMemberHandling = MissingMemberHandling.Ignore,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
Converters =
{
new JsonStringEnumConverterFactory(),
new SearchResultConverterFactory()
}
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -12,6 +13,7 @@
using Algolia.Search.Serializer;
using Algolia.Search.Utils;
using Microsoft.Extensions.Logging;
using JsonSerializer = System.Text.Json.JsonSerializer;

namespace Algolia.Search.Transport;

Expand Down Expand Up @@ -154,7 +156,7 @@ private async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod metho
response.Body.Seek(0, SeekOrigin.Begin);
}

var deserialized = await _serializer.Deserialize<TResult>(response.Body);
var deserialized = await _serializer.Deserialize<TResult>(response.Body).ConfigureAwait(false);

if (_logger.IsEnabled(LogLevel.Trace))
{
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public void processOpts() {
file.getTemplateFile().equals("AbstractOpenAPISchema.mustache") ||
file.getTemplateFile().equals("ApiResponse.mustache") ||
file.getTemplateFile().equals("Multimap.mustache") ||
file.getTemplateFile().equals("modelInnerEnum.mustache") ||
file.getTemplateFile().equals("ApiException.mustache") ||
file.getTemplateFile().equals("GlobalConfiguration.mustache") ||
file.getTemplateFile().equals("IReadableConfiguration.mustache") ||
Expand Down
10 changes: 6 additions & 4 deletions playground/csharp/Performances/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Diagnostics;
using System.Text.Json;
using Algolia.Search.Clients;
using Algolia.Search.Models.Search;
using Algolia.Search.Transport;
using Newtonsoft.Json;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
Expand Down Expand Up @@ -40,7 +40,7 @@
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithBody(JsonConvert.SerializeObject(new BatchResponse(1, new List<string>())))
.WithBody(JsonSerializer.Serialize(new BatchResponse(1, [])))
);


Expand All @@ -53,7 +53,8 @@
const int iterations = 10000;
Stopwatch stopwatch = new();

Console.WriteLine($"Calling SearchAsync {iterations} times, with a response payload of {searchResults.Length / 1024} ko");
Console.WriteLine(
$"Calling SearchAsync {iterations} times, with a response payload of {searchResults.Length / 1024} ko");

stopwatch.Start();
for (var i = 0; i < iterations; i++)
Expand All @@ -75,7 +76,8 @@ await client

const int iterationsBatch = 100;
const int iterationsRecords = 1000;
Console.WriteLine($"Saving {iterationsBatch*iterationsRecords} records by calling BatchAsync {iterationsBatch} times, with request payload containing {iterationsRecords} record of {JsonConvert.SerializeObject(CreateBody(0)).Length / 1024} ko.");
Console.WriteLine(
$"Saving {iterationsBatch * iterationsRecords} records by calling BatchAsync {iterationsBatch} times, with request payload containing {iterationsRecords} record of {JsonSerializer.Serialize(CreateBody(0)).Length / 1024} ko.");

stopwatch.Restart();
for (var i = 0; i < iterationsBatch; i++)
Expand Down
3 changes: 0 additions & 3 deletions playground/csharp/Playground/Model/TestObject.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
using System.Runtime.Serialization;
using Algolia.Search.Models.Search;

public class TestObject : Hit
{
[DataMember(Name = "value")]
public string? Value { get; set; }

[DataMember(Name = "otherValue")]
public string? OtherValue { get; set; }
}
Loading

0 comments on commit ee14887

Please sign in to comment.