Skip to content

Commit 86af828

Browse files
authored
Replace Newtonsoft.Json with System.Text.Json (#109)
* Removed package, now builds. - IgnoreNullValues is obsolete, i kept is because it IgnoreCondition is >net7 - Removed package from core, and readded package in MongoDbProvider, only used there. Test project(s) still use newtonsoft.json, but we can evaluate this later. * feat(error): Fix test and use problem details - https://andrewlock.net/creating-a-custom-error-handler-middleware-function/ * chore: Replace jsonnet with stj in test projects. ---------
1 parent e9dd539 commit 86af828

File tree

13 files changed

+143
-67
lines changed

13 files changed

+143
-67
lines changed

src/Serilog.Ui.Core/Serilog.Ui.Core.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
11-
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
1211
</ItemGroup>
1312
</Project>

src/Serilog.Ui.MongoDbProvider/Serilog.Ui.MongoDbProvider.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
1111
<PackageReference Include="MongoDB.Driver" Version="2.21.0" />
12+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
1213
</ItemGroup>
1314

1415
<ItemGroup>

src/Serilog.Ui.Web/Endpoints/SerilogUiAppRoutes.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.AspNetCore.Http.Extensions;
3-
using Newtonsoft.Json;
3+
using System.Text.Json;
44
using System;
55
using System.IO;
66
using System.Text;
77
using System.Threading.Tasks;
8-
using Newtonsoft.Json.Serialization;
98
using Ardalis.GuardClauses;
109

1110
namespace Serilog.Ui.Web.Endpoints
1211
{
1312
internal class SerilogUiAppRoutes : ISerilogUiAppRoutes
1413
{
15-
private static readonly JsonSerializerSettings _jsonSerializerOptions = new()
14+
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
1615
{
17-
NullValueHandling = NullValueHandling.Ignore,
18-
ContractResolver = new CamelCasePropertyNamesContractResolver(),
19-
Formatting = Formatting.None
16+
IgnoreNullValues = true,
17+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
2018
};
19+
2120
private readonly IAppStreamLoader _streamLoader;
2221

2322
public SerilogUiAppRoutes(IAppStreamLoader appStreamLoader)
@@ -67,7 +66,7 @@ private static async Task<string> LoadStream(Stream stream, UiOptions options)
6766
{
6867
var htmlStringBuilder = new StringBuilder(await new StreamReader(stream).ReadToEndAsync());
6968
var authType = options.Authorization.AuthenticationType.ToString();
70-
var encodeAuthOpts = Uri.EscapeDataString(JsonConvert.SerializeObject(new { options.RoutePrefix, authType, options.HomeUrl }, _jsonSerializerOptions));
69+
var encodeAuthOpts = Uri.EscapeDataString(JsonSerializer.Serialize(new { options.RoutePrefix, authType, options.HomeUrl }, JsonSerializerOptions));
7170

7271
htmlStringBuilder
7372
.Replace("%(Configs)", encodeAuthOpts)

src/Serilog.Ui.Web/Endpoints/SerilogUiEndpoints.cs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.Logging;
4-
using Newtonsoft.Json;
5-
using Newtonsoft.Json.Serialization;
4+
using System.Text.Json;
65
using Serilog.Ui.Core;
76
using System;
87
using System.Globalization;
98
using System.Linq;
109
using System.Net;
1110
using System.Threading.Tasks;
11+
using Microsoft.AspNetCore.Mvc;
1212

1313
namespace Serilog.Ui.Web.Endpoints
1414
{
1515
internal class SerilogUiEndpoints : ISerilogUiEndpoints
1616
{
1717
private readonly ILogger<SerilogUiEndpoints> _logger;
18-
private static readonly JsonSerializerSettings _jsonSerializerOptions = new()
18+
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
1919
{
20-
NullValueHandling = NullValueHandling.Ignore,
21-
ContractResolver = new CamelCasePropertyNamesContractResolver(),
22-
Formatting = Formatting.None
20+
IgnoreNullValues = true,
21+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
2322
};
23+
2424
private string[] _providerKeys;
2525

2626
public SerilogUiEndpoints(ILogger<SerilogUiEndpoints> logger)
@@ -41,7 +41,7 @@ public async Task GetApiKeysAsync(HttpContext httpContext)
4141
_providerKeys = aggregateDataProvider.Keys.ToArray();
4242
}
4343

44-
var result = JsonConvert.SerializeObject(_providerKeys);
44+
var result = JsonSerializer.Serialize(_providerKeys, JsonSerializerOptions);
4545
httpContext.Response.StatusCode = (int)HttpStatusCode.OK;
4646
await httpContext.Response.WriteAsync(result);
4747
}
@@ -85,7 +85,7 @@ private static async Task<string> FetchLogsAsync(HttpContext httpContext)
8585

8686
var (logs, total) = await provider.FetchDataAsync(currentPage, count, level, textSearch, start, end);
8787

88-
var result = JsonConvert.SerializeObject(new { logs, total, count, currentPage }, _jsonSerializerOptions);
88+
var result = JsonSerializer.Serialize(new { logs, total, count, currentPage }, JsonSerializerOptions);
8989

9090
return result;
9191
}
@@ -113,16 +113,38 @@ private static (int currPage, int count, string dbKey, string level, string text
113113
return (currentPage, currentCount, keyStr, levelStr, searchStr, outputStartDate, outputEndDate);
114114
}
115115

116+
/// <summary>
117+
/// Returns an industry standard ProblemDetails object.
118+
/// See: <see href="https://datatracker.ietf.org/doc/html/rfc7807"/>
119+
/// </summary>
120+
/// <param name="httpContext"></param>
121+
/// <param name="ex"></param>
122+
/// <returns></returns>
116123
private Task OnError(HttpContext httpContext, Exception ex)
117124
{
118125
_logger.LogError(ex, "@Message", ex.Message);
119-
httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
120126

121-
var errorMessage = httpContext.Request.IsLocal()
122-
? JsonConvert.SerializeObject(new { errorMessage = ex.Message })
123-
: JsonConvert.SerializeObject(new { errorMessage = "Internal server error" });
127+
httpContext.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
128+
httpContext.Response.ContentType = "application/problem+json";
129+
130+
var includeDetails = httpContext.Request.IsLocal();
131+
132+
var title = includeDetails ? "An error occured: " + ex.Message : "An error occured";
133+
var details = includeDetails ? ex.ToString() : null;
134+
135+
var problem = new ProblemDetails
136+
{
137+
Status = httpContext.Response.StatusCode,
138+
Title = title,
139+
Detail = details,
140+
Extensions =
141+
{
142+
["traceId"] = httpContext.TraceIdentifier
143+
}
144+
};
124145

125-
return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new { errorMessage }));
146+
var stream = httpContext.Response.Body;
147+
return JsonSerializer.SerializeAsync(stream, problem);
126148
}
127149
}
128150
}

src/Serilog.Ui.Web/Serilog.UI.nuspec

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,21 @@
1919
<group targetFramework=".NETCoreApp3.1">
2020
<dependency id="Ardalis.GuardClauses" version="4.0.1" exclude="Build,Analyzers" />
2121
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="3.1.3" exclude="Build,Analyzers" />
22-
<dependency id="Newtonsoft.Json" version="13.0.3" exclude="Build,Analyzers" />
2322
<dependency id="Scrutor" version="4.2.2" exclude="Build,Analyzers" />
2423
</group>
2524
<group targetFramework=".NET5.0">
2625
<dependency id="Ardalis.GuardClauses" version="4.0.1" exclude="Build,Analyzers" />
2726
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="5.0.0" exclude="Build,Analyzers" />
28-
<dependency id="Newtonsoft.Json" version="13.0.3" exclude="Build,Analyzers" />
2927
<dependency id="Scrutor" version="4.2.2" exclude="Build,Analyzers" />
3028
</group>
3129
<group targetFramework=".NET6.0">
3230
<dependency id="Ardalis.GuardClauses" version="4.0.1" exclude="Build,Analyzers" />
3331
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="6.0.0" exclude="Build,Analyzers" />
34-
<dependency id="Newtonsoft.Json" version="13.0.3" exclude="Build,Analyzers" />
3532
<dependency id="Scrutor" version="4.2.2" exclude="Build,Analyzers" />
3633
</group>
3734
<group targetFramework=".NET7.0">
3835
<dependency id="Ardalis.GuardClauses" version="4.0.1" exclude="Build,Analyzers" />
3936
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="7.0.0" exclude="Build,Analyzers" />
40-
<dependency id="Newtonsoft.Json" version="13.0.3" exclude="Build,Analyzers" />
4137
<dependency id="Scrutor" version="4.2.2" exclude="Build,Analyzers" />
4238
</group>
4339
</dependencies>

tests/Serilog.Ui.Common.Tests/DataSamples/LogModelFaker.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Bogus;
2-
using Newtonsoft.Json;
32
using Serilog.Ui.Core;
43
using System;
54
using System.Collections.Generic;
@@ -22,7 +21,7 @@ private static Faker<LogModel> LogsRules()
2221
.RuleFor(p => p.RowNo, f => f.Database.Random.Int())
2322
.RuleFor(p => p.Level, f => f.PickRandom(LogLevels))
2423
.RuleFor(p => p.Properties, PropertiesFaker.SerializedProperties)
25-
.RuleFor(p => p.Exception, (f) => JsonConvert.SerializeObject(f.System.Exception()))
24+
.RuleFor(p => p.Exception, (f) => f.System.Exception().ToString())
2625
.RuleFor(p => p.Message, f => f.System.Random.Words(6))
2726
.RuleFor(p => p.PropertyType, f => f.System.CommonFileType())
2827
.RuleFor(p => p.Timestamp, f => new DateTime(f.Date.Recent().Ticks, DateTimeKind.Utc));

tests/Serilog.Ui.Common.Tests/DataSamples/LogModelPropsCollector.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ namespace Serilog.Ui.Common.Tests.DataSamples
88
public class LogModelPropsCollector
99
{
1010
public LogModelPropsCollector(IEnumerable<LogModel> models)
11+
: this(models.ToList())
12+
{
13+
}
14+
15+
public LogModelPropsCollector(ICollection<LogModel> models)
1116
{
1217
DataSet = models.ToList();
1318
Collect(models);
@@ -19,7 +24,7 @@ public LogModelPropsCollector(IEnumerable<LogModel> models)
1924
public IEnumerable<DateTime> TimesSamples { get; private set; }
2025
public IEnumerable<string> MessagePiecesSamples { get; private set; }
2126

22-
private void Collect(IEnumerable<LogModel> models)
27+
private void Collect(ICollection<LogModel> models)
2328
{
2429
Example = models.First();
2530

@@ -29,15 +34,15 @@ private void Collect(IEnumerable<LogModel> models)
2934

3035
MessagePiecesSamples = new List<string>
3136
{
32-
models.ElementAtOrDefault(0).Message,
33-
models.ElementAtOrDefault(1).Message.Substring(1, models.ElementAtOrDefault(1).Message.Length / 2),
34-
models.ElementAtOrDefault(2).Message.Substring(1, models.ElementAtOrDefault(2).Message.Length / 2),
37+
models.ElementAt(0).Message,
38+
models.ElementAt(1).Message.Substring(1, models.ElementAt(1).Message.Length / 2),
39+
models.ElementAt(2).Message.Substring(1, models.ElementAt(2).Message.Length / 2),
3540
};
3641
TimesSamples = new List<DateTime>
3742
{
38-
models.ElementAtOrDefault(0).Timestamp,
39-
models.ElementAtOrDefault(1).Timestamp,
40-
models.ElementAtOrDefault(2).Timestamp,
43+
models.ElementAt(0).Timestamp,
44+
models.ElementAt(1).Timestamp,
45+
models.ElementAt(2).Timestamp,
4146
}.OrderBy(p => p.Ticks);
4247
}
4348
}

tests/Serilog.Ui.Common.Tests/DataSamples/MongoDbLogModelFaker.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
using MongoDB.Bson;
2-
using Newtonsoft.Json;
32
using Serilog.Ui.Common.Tests.FakeObjectModels;
43
using Serilog.Ui.MongoDbProvider;
5-
using System;
64
using System.Collections.Generic;
75
using System.Linq;
6+
using System.Text.Json;
7+
using Bogus;
88

99
namespace Serilog.Ui.Common.Tests.DataSamples
1010
{
1111
public static class MongoDbLogModelFaker
1212
{
1313
public static (IEnumerable<MongoDbLogModel> logs, LogModelPropsCollector collector) Logs(int generationCount)
1414
{
15-
var originalLogs = LogModelFaker.Logs(generationCount);
15+
var originalLogs = LogModelFaker.Logs(generationCount)
16+
.ToList();
17+
1618
var modelCollector = new LogModelPropsCollector(originalLogs);
17-
return (originalLogs.Select(p => new MongoDbLogModel
19+
20+
var faker = new Faker();
21+
22+
var logs = originalLogs.Select(p => new MongoDbLogModel
1823
{
1924
Id = p.RowNo,
2025
Level = p.Level,
2126
RenderedMessage = p.Message,
2227
Timestamp = p.Timestamp,
2328
UtcTimeStamp = p.Timestamp.ToUniversalTime(),
24-
Properties = JsonConvert.DeserializeObject<Properties>(p.Properties),
25-
Exception = JsonConvert.DeserializeObject<Exception>(p.Exception).ToBsonDocument(),
26-
}), modelCollector);
29+
Properties = JsonSerializer.Deserialize<Properties>(p.Properties),
30+
Exception = faker.System.Exception() // Serialization round-trip not possible for an exception, so we generate a new exception.
31+
.ToBsonDocument(),
32+
});
33+
34+
return (logs, modelCollector);
2735
}
2836

2937
public static (IEnumerable<MongoDbLogModel> logs, LogModelPropsCollector collector) Logs() => Logs(20);
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
using Bogus;
1+
using System.Text.Json;
2+
using Bogus;
23
using Microsoft.Extensions.Logging;
3-
using Newtonsoft.Json;
44
using Serilog.Ui.Common.Tests.FakeObjectModels;
55

66
namespace Serilog.Ui.Common.Tests.DataSamples
77
{
88
internal static class PropertiesFaker
99
{
10-
internal static Faker<Properties> Properties = new Faker<Properties>()
11-
.RuleFor(p => p.EventId, f => JsonConvert.SerializeObject(new EventId(f.Random.Int(), f.Hacker.Noun())))
10+
private static readonly Faker<Properties> Properties = new Faker<Properties>()
11+
.RuleFor(p => p.EventId, f => new EventId(f.Random.Int(), f.Hacker.Noun()))
1212
.RuleForType(typeof(string), p => p.System.Random.Words(3));
1313

14-
internal static string SerializedProperties = JsonConvert.SerializeObject(Properties.Generate());
14+
internal static readonly string SerializedProperties = JsonSerializer.Serialize(Properties.Generate());
1515
}
1616
}
Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
1-
namespace Serilog.Ui.Common.Tests.FakeObjectModels
1+
using System;
2+
using System.Text.Json;
3+
using System.Text.Json.Nodes;
4+
using System.Text.Json.Serialization;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Serilog.Ui.Common.Tests.FakeObjectModels
28
{
39
internal class Properties
410
{
511
public string SourceContext { get; set; }
612

7-
public object EventId { get; set; }
13+
[JsonConverter(typeof(EventIdConverter))]
14+
public EventId EventId { get; set; }
815

916
public string Protocol { get; set; }
1017

1118
public string Host { get; set; }
1219
}
20+
21+
/// <summary>
22+
/// A custom converter for the <see cref="EventId"/> class, since constructing an immutable struct type is not supported when [JsonConstructor] is not used.
23+
/// See: <see href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/immutability"/>
24+
/// </summary>
25+
internal class EventIdConverter : JsonConverter<EventId>
26+
{
27+
public override EventId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
28+
{
29+
var obj = (JsonObject) JsonNode.Parse(ref reader) ?? throw new JsonException();
30+
31+
var id = (obj[nameof(EventId.Id)] ?? throw new JsonException()).GetValue<int>();
32+
var name = (obj[nameof(EventId.Name)] ?? throw new JsonException()).GetValue<string>();
33+
return new EventId(id, name);
34+
}
35+
36+
public override void Write(Utf8JsonWriter writer, EventId value, JsonSerializerOptions options)
37+
{
38+
// STJ handles writing fine, so we just delegate to the default implementation.
39+
((JsonConverter<EventId>)JsonSerializerOptions.Default.GetConverter(typeof(EventId))).Write(writer, value, options);
40+
}
41+
}
1342
}

tests/Serilog.Ui.PostgreSqlProvider.Tests/Util/PostgresTestProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ protected override async Task CheckDbReadinessAsync()
4242

4343
protected override async Task InitializeAdditionalAsync()
4444
{
45-
var logs = LogModelFaker.Logs(100);
45+
var logs = LogModelFaker.Logs(100)
46+
.ToList();
4647

4748
// manual conversion due to current implementation, based on a INT level column
4849
var postgresTableLogs = logs.Select(p => new

0 commit comments

Comments
 (0)