Skip to content

Commit ee14887

Browse files
authored
feat(csharp): remove Newtonsoft JSON.NET (#2717)
1 parent 0bafedc commit ee14887

File tree

24 files changed

+354
-628
lines changed

24 files changed

+354
-628
lines changed

.github/.cache_version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.0.13
1+
1.0.14

clients/algoliasearch-client-csharp/algoliasearch/Http/ClientUtils.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Globalization;
55
using System.Linq;
66
using System.Runtime.Serialization;
7+
using System.Text.Json.Serialization;
78
using Algolia.Search.Models.Common;
89

910
namespace Algolia.Search.Http;
@@ -54,7 +55,7 @@ private static bool HasEnumMemberAttrValue(object enumVal)
5455
throw new ArgumentNullException(nameof(enumVal));
5556
var enumType = enumVal.GetType();
5657
var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
57-
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<EnumMemberAttribute>().FirstOrDefault();
58+
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<JsonPropertyNameAttribute>().FirstOrDefault();
5859
return attr != null;
5960
}
6061

@@ -69,7 +70,7 @@ private static string GetEnumMemberAttrValue(object enumVal)
6970
throw new ArgumentNullException(nameof(enumVal));
7071
var enumType = enumVal.GetType();
7172
var memInfo = enumType.GetMember(enumVal.ToString() ?? throw new InvalidOperationException());
72-
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<EnumMemberAttribute>().FirstOrDefault();
73-
return attr?.Value;
73+
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<JsonPropertyNameAttribute>().FirstOrDefault();
74+
return attr?.Name;
7475
}
7576
}

clients/algoliasearch-client-csharp/algoliasearch/Http/EchoHttpRequester.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Text;
56
using System.Threading;
67
using System.Threading.Tasks;
78
using System.Web;

clients/algoliasearch-client-csharp/algoliasearch/Serializer/DefaultSerializer.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
22
using System.IO;
3+
using System.Runtime.CompilerServices;
4+
using System.Text.Json;
35
using System.Threading.Tasks;
46
using Algolia.Search.Exceptions;
57
using Algolia.Search.Models.Common;
68
using Microsoft.Extensions.Logging;
7-
using Newtonsoft.Json;
9+
810

911
namespace Algolia.Search.Serializer;
1012

@@ -26,7 +28,7 @@ public string Serialize(object data)
2628
{
2729
if (data is not AbstractSchema schema)
2830
{
29-
return JsonConvert.SerializeObject(data, JsonConfig.AlgoliaJsonSerializerSettings);
31+
return JsonSerializer.Serialize(data, JsonConfig.Options);
3032
}
3133

3234
// the object to be serialized is a oneOf/anyOf schema
@@ -51,8 +53,12 @@ private async Task<object> Deserialize(Stream response, Type type)
5153
{
5254
using var reader = new StreamReader(response);
5355
var readToEndAsync = await reader.ReadToEndAsync().ConfigureAwait(false);
54-
return JsonConvert.DeserializeObject(readToEndAsync, type,
55-
JsonConfig.AlgoliaJsonSerializerSettings);
56+
if (string.IsNullOrEmpty(readToEndAsync))
57+
{
58+
return null;
59+
}
60+
61+
return JsonSerializer.Deserialize(readToEndAsync, type, JsonConfig.Options);
5662
}
5763
catch (Exception ex)
5864
{
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
7+
namespace Algolia.Search.Serializer;
8+
9+
/// <summary>
10+
/// Factory to create a JsonConverter for enums
11+
/// </summary>
12+
public class JsonStringEnumConverterFactory : JsonConverterFactory
13+
{
14+
/// <summary>
15+
/// Check if the type is an enum
16+
/// </summary>
17+
/// <param name="typeToConvert"></param>
18+
/// <returns></returns>
19+
public override bool CanConvert(Type typeToConvert)
20+
{
21+
return typeToConvert.IsEnum;
22+
}
23+
24+
/// <summary>
25+
/// Create a new converter
26+
/// </summary>
27+
/// <param name="typeToConvert"></param>
28+
/// <param name="options"></param>
29+
/// <returns></returns>
30+
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
31+
{
32+
var type = typeof(JsonStringEnumConverter<>).MakeGenericType(typeToConvert);
33+
return (JsonConverter)Activator.CreateInstance(type)!;
34+
}
35+
}
36+
37+
/// <summary>
38+
/// Custom JsonConverter to convert enum to string, using the JsonPropertyNameAttribute if present
39+
/// </summary>
40+
/// <typeparam name="TEnum"></typeparam>
41+
public class JsonStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
42+
{
43+
private readonly Dictionary<TEnum, string> _enumToString = new();
44+
private readonly Dictionary<string, TEnum> _stringToEnum = new();
45+
private readonly Dictionary<int, TEnum> _numberToEnum = new();
46+
47+
/// <summary>
48+
/// Constructor to create the converter
49+
/// </summary>
50+
public JsonStringEnumConverter()
51+
{
52+
var type = typeof(TEnum);
53+
foreach (var value in Enum.GetValues(type))
54+
{
55+
var enumMember = type.GetMember(value.ToString())[0];
56+
var attr = enumMember.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false)
57+
.Cast<JsonPropertyNameAttribute>()
58+
.FirstOrDefault();
59+
60+
var num = Convert.ToInt32(type.GetField("value__")?.GetValue(value));
61+
if (attr?.Name != null)
62+
{
63+
_enumToString.Add((TEnum)value, attr.Name);
64+
_stringToEnum.Add(attr.Name, (TEnum)value);
65+
_numberToEnum.Add(num, (TEnum)value);
66+
}
67+
else
68+
{
69+
_enumToString.Add((TEnum)value, value.ToString());
70+
_stringToEnum.Add(value.ToString(), (TEnum)value);
71+
_numberToEnum.Add(num, (TEnum)value);
72+
}
73+
}
74+
}
75+
76+
77+
/// <summary>
78+
/// Read the enum from the json
79+
/// </summary>
80+
/// <param name="reader"></param>
81+
/// <param name="typeToConvert"></param>
82+
/// <param name="options"></param>
83+
/// <returns></returns>
84+
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
85+
{
86+
var type = reader.TokenType;
87+
if (type == JsonTokenType.String)
88+
{
89+
var stringValue = reader.GetString();
90+
91+
if (stringValue != null && _stringToEnum.TryGetValue(stringValue, out var enumValue))
92+
{
93+
return enumValue;
94+
}
95+
}
96+
else if (type == JsonTokenType.Number)
97+
{
98+
var numValue = reader.GetInt32();
99+
_numberToEnum.TryGetValue(numValue, out var enumValue);
100+
return enumValue;
101+
}
102+
103+
return default;
104+
}
105+
106+
/// <summary>
107+
/// Write the enum to the json
108+
/// </summary>
109+
/// <param name="writer"></param>
110+
/// <param name="value"></param>
111+
/// <param name="options"></param>
112+
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
113+
{
114+
var success = _enumToString.TryGetValue(value, out var stringValue);
115+
if (success)
116+
{
117+
writer.WriteStringValue(stringValue);
118+
}
119+
else
120+
{
121+
writer.WriteNullValue();
122+
}
123+
}
124+
}
Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using Newtonsoft.Json;
2-
using Newtonsoft.Json.Serialization;
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
using Algolia.Search.Models.Search;
34

45
namespace Algolia.Search.Serializer;
56

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

13-
private static readonly DefaultContractResolver Resolver = new() { NamingStrategy = new CamelCaseNamingStrategy() };
14-
15-
public static readonly JsonSerializerSettings AlgoliaJsonSerializerSettings = new()
14+
public static readonly JsonSerializerOptions Options = new()
1615
{
17-
Formatting = Formatting.None,
18-
NullValueHandling = NullValueHandling.Ignore,
19-
ContractResolver = Resolver,
20-
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
21-
DateParseHandling = DateParseHandling.DateTime,
22-
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
23-
MissingMemberHandling = MissingMemberHandling.Ignore,
16+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
17+
WriteIndented = false,
18+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
19+
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
20+
Converters =
21+
{
22+
new JsonStringEnumConverterFactory(),
23+
new SearchResultConverterFactory()
24+
}
2425
};
2526
}

clients/algoliasearch-client-csharp/algoliasearch/Transport/HttpTransport.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
@@ -12,6 +13,7 @@
1213
using Algolia.Search.Serializer;
1314
using Algolia.Search.Utils;
1415
using Microsoft.Extensions.Logging;
16+
using JsonSerializer = System.Text.Json.JsonSerializer;
1517

1618
namespace Algolia.Search.Transport;
1719

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

157-
var deserialized = await _serializer.Deserialize<TResult>(response.Body);
159+
var deserialized = await _serializer.Deserialize<TResult>(response.Body).ConfigureAwait(false);
158160

159161
if (_logger.IsEnabled(LogLevel.Trace))
160162
{

clients/algoliasearch-client-csharp/algoliasearch/Utils/ApiKeyEquals.cs

Lines changed: 0 additions & 59 deletions
This file was deleted.

generators/src/main/java/com/algolia/codegen/AlgoliaCSharpGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public void processOpts() {
120120
file.getTemplateFile().equals("AbstractOpenAPISchema.mustache") ||
121121
file.getTemplateFile().equals("ApiResponse.mustache") ||
122122
file.getTemplateFile().equals("Multimap.mustache") ||
123+
file.getTemplateFile().equals("modelInnerEnum.mustache") ||
123124
file.getTemplateFile().equals("ApiException.mustache") ||
124125
file.getTemplateFile().equals("GlobalConfiguration.mustache") ||
125126
file.getTemplateFile().equals("IReadableConfiguration.mustache") ||

playground/csharp/Performances/Program.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System.Diagnostics;
2+
using System.Text.Json;
23
using Algolia.Search.Clients;
34
using Algolia.Search.Models.Search;
45
using Algolia.Search.Transport;
5-
using Newtonsoft.Json;
66
using WireMock.RequestBuilders;
77
using WireMock.ResponseBuilders;
88
using WireMock.Server;
@@ -40,7 +40,7 @@
4040
.RespondWith(
4141
Response.Create()
4242
.WithStatusCode(200)
43-
.WithBody(JsonConvert.SerializeObject(new BatchResponse(1, new List<string>())))
43+
.WithBody(JsonSerializer.Serialize(new BatchResponse(1, [])))
4444
);
4545

4646

@@ -53,7 +53,8 @@
5353
const int iterations = 10000;
5454
Stopwatch stopwatch = new();
5555

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

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

7677
const int iterationsBatch = 100;
7778
const int iterationsRecords = 1000;
78-
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.");
79+
Console.WriteLine(
80+
$"Saving {iterationsBatch * iterationsRecords} records by calling BatchAsync {iterationsBatch} times, with request payload containing {iterationsRecords} record of {JsonSerializer.Serialize(CreateBody(0)).Length / 1024} ko.");
7981

8082
stopwatch.Restart();
8183
for (var i = 0; i < iterationsBatch; i++)

0 commit comments

Comments
 (0)