Replace JSON.NET with System.Text.json (#840)

This commit is contained in:
RobertBeekman authored and GitHub committed 2024-02-28 19:31:38 +01:00
1 parent e112ca9da8
commit a340f8822e
63 files changed
+466 -584

No files matched your search

@@ -1,46 +0,0 @@
using System.Globalization;
using Newtonsoft.Json;
namespace Artemis.Core.Services.Core;
/// <summary>
/// Represents build information related to the currently running Artemis build
/// </summary>
public class BuildInfo
{
/// <summary>
/// Gets the unique ID of this build
/// </summary>
[JsonProperty]
public int BuildId { get; internal set; }
/// <summary>
/// Gets the build number. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
[JsonProperty]
public double BuildNumber { get; internal set; }
/// <summary>
/// Gets the build number formatted as a string. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
public string BuildNumberDisplay => BuildNumber.ToString(CultureInfo.InvariantCulture);
/// <summary>
/// Gets the branch of the triggering repo the build was created for.
/// </summary>
[JsonProperty]
public string SourceBranch { get; internal set; } = null!;
/// <summary>
/// Gets the commit ID used to create this build
/// </summary>
[JsonProperty]
public string SourceVersion { get; internal set; } = null!;
/// <summary>
/// Gets a boolean indicating whether the current build is a local build
/// </summary>
public bool IsLocalBuild { get; internal set; }
}
@@ -5,7 +5,6 @@ using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using Artemis.Core.Modules;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services;
+2 -3
View File
@@ -4,7 +4,6 @@ using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Artemis.Storage.Entities.Profile.Nodes;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core.Services;
@@ -42,12 +41,12 @@ internal class NodeService : INodeService
public string ExportScript(NodeScript nodeScript)
{
nodeScript.Save();
return JsonConvert.SerializeObject(nodeScript.Entity, IProfileService.ExportSettings);
return CoreJson.Serialize(nodeScript.Entity);
}
public void ImportScript(string json, NodeScript target)
{
NodeScriptEntity? entity = JsonConvert.DeserializeObject<NodeScriptEntity>(json);
NodeScriptEntity? entity = CoreJson.Deserialize<NodeScriptEntity>(json);
if (entity == null)
throw new ArtemisCoreException("Failed to load node script");
@@ -127,7 +127,7 @@ internal class PluginManagementService : IPluginManagementService
throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName);
using StreamReader reader = new(metaDataFileEntry.Open());
PluginInfo builtInPluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
PluginInfo builtInPluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
string preferred = builtInPluginInfo.PreferredPluginDirectory;
// Find the matching plugin in the plugin folder
@@ -360,7 +360,7 @@ internal class PluginManagementService : IPluginManagementService
_logger.Warning(new ArtemisPluginException("Couldn't find the plugins metadata file at " + metadataFile), "Plugin exception");
// PluginInfo contains the ID which we need to move on
PluginInfo pluginInfo = CoreJson.DeserializeObject<PluginInfo>(File.ReadAllText(metadataFile))!;
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(File.ReadAllText(metadataFile))!;
if (!pluginInfo.IsCompatible)
return null;
@@ -610,7 +610,7 @@ internal class PluginManagementService : IPluginManagementService
throw new ArtemisPluginException("Couldn't find a plugin.json in " + fileName);
using StreamReader reader = new(metaDataFileEntry.Open());
PluginInfo pluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
PluginInfo pluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
if (!pluginInfo.Main.EndsWith(".dll"))
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
@@ -2,7 +2,6 @@
using System.Collections.ObjectModel;
using System.IO;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core.Services;
@@ -12,11 +11,6 @@ namespace Artemis.Core.Services;
/// </summary>
public interface IProfileService : IArtemisService
{
/// <summary>
/// Gets the JSON serializer settings used to import/export profiles.
/// </summary>
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
/// <summary>
/// Gets a read only collection containing all the profile categories.
/// </summary>
@@ -5,13 +5,13 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Migrations;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serilog;
using SkiaSharp;
@@ -410,8 +410,8 @@ internal class ProfileService : IProfileService
if (profileEntity == null)
throw new ArtemisCoreException("Could not locate profile entity");
string configurationJson = JsonConvert.SerializeObject(profileConfiguration.Entity, IProfileService.ExportSettings);
string profileJson = JsonConvert.SerializeObject(profileEntity, IProfileService.ExportSettings);
string configurationJson = CoreJson.Serialize(profileConfiguration.Entity);
string profileJson = CoreJson.Serialize(profileEntity);
MemoryStream archiveStream = new();
@@ -461,21 +461,21 @@ internal class ProfileService : IProfileService
// Deserialize profile configuration to JObject
await using Stream configurationStream = configurationEntry.Open();
using StreamReader configurationReader = new(configurationStream);
JObject? configurationJson = JsonConvert.DeserializeObject<JObject>(await configurationReader.ReadToEndAsync(), IProfileService.ExportSettings);
JsonObject? configurationJson = CoreJson.Deserialize<JsonObject>(await configurationReader.ReadToEndAsync());
// Deserialize profile to JObject
await using Stream profileStream = profileEntry.Open();
using StreamReader profileReader = new(profileStream);
JObject? profileJson = JsonConvert.DeserializeObject<JObject>(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
JsonObject? profileJson = CoreJson.Deserialize<JsonObject>(await profileReader.ReadToEndAsync());
// Before deserializing, apply any pending migrations
MigrateProfile(configurationJson, profileJson);
// Deserialize profile configuration to ProfileConfigurationEntity
ProfileConfigurationEntity? configurationEntity = configurationJson?.ToObject<ProfileConfigurationEntity>(JsonSerializer.Create(IProfileService.ExportSettings));
ProfileConfigurationEntity? configurationEntity = configurationJson?.Deserialize<ProfileConfigurationEntity>(Constants.JsonConvertSettings);
if (configurationEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize configuration.json");
// Deserialize profile to ProfileEntity
ProfileEntity? profileEntity = profileJson?.ToObject<ProfileEntity>(JsonSerializer.Create(IProfileService.ExportSettings));
ProfileEntity? profileEntity = profileJson?.Deserialize<ProfileEntity>(Constants.JsonConvertSettings);
if (profileEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json");
@@ -559,7 +559,7 @@ internal class ProfileService : IProfileService
}
}
private void MigrateProfile(JObject? configurationJson, JObject? profileJson)
private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson)
{
if (configurationJson == null || profileJson == null)
return;
@@ -568,7 +568,7 @@ internal class ProfileService : IProfileService
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
{
if (profileMigrator.Version <= configurationJson["Version"]!.Value<int>())
if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>())
continue;
profileMigrator.Migrate(configurationJson, profileJson);
@@ -1,9 +1,13 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services;
@@ -15,10 +19,12 @@ namespace Artemis.Core.Services;
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel, new()
{
private readonly Module<T> _module;
private readonly Action<T, T> _update;
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
_module = module ?? throw new ArgumentNullException(nameof(module));
_update = CreateUpdateAction();
ThrowOnFail = true;
Accepts = MimeType.Json;
@@ -31,8 +37,6 @@ public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
@@ -44,7 +48,9 @@ public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
using TextReader reader = context.OpenRequestText();
try
{
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
T? dataModel = CoreJson.Deserialize<T>(await reader.ReadToEndAsync());
if (dataModel != null)
_update(dataModel, _module.DataModel);
}
catch (JsonException)
{
@@ -53,5 +59,26 @@ public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
}
}
#endregion
private Action<T, T> CreateUpdateAction()
{
ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
ParameterExpression targetParameter = Expression.Parameter(typeof(T), "target");
IEnumerable<BinaryExpression> assignments = typeof(T)
.GetProperties()
.Where(prop => prop.CanWrite && prop.GetSetMethod() != null &&
prop.GetSetMethod()!.IsPublic &&
!prop.IsDefined(typeof(JsonIgnoreAttribute), false) &&
!prop.PropertyType.IsAssignableTo(typeof(IDataModelEvent)))
.Select(prop =>
{
MemberExpression sourceProperty = Expression.Property(sourceParameter, prop);
MemberExpression targetProperty = Expression.Property(targetParameter, prop);
return Expression.Assign(targetProperty, sourceProperty);
});
BlockExpression body = Expression.Block(assignments);
return Expression.Lambda<Action<T, T>>(body, sourceParameter, targetParameter).Compile();
}
}
@@ -1,8 +1,8 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services;
@@ -52,7 +52,7 @@ public class JsonPluginEndPoint<T> : PluginEndPoint
object? response = null;
try
{
T? deserialized = JsonConvert.DeserializeObject<T>(await reader.ReadToEndAsync());
T? deserialized = JsonSerializer.Deserialize<T>(await reader.ReadToEndAsync());
if (deserialized == null)
throw new JsonException("Deserialization returned null");
@@ -74,7 +74,7 @@ public class JsonPluginEndPoint<T> : PluginEndPoint
}
await using TextWriter writer = context.OpenResponseText();
await writer.WriteAsync(JsonConvert.SerializeObject(response));
await writer.WriteAsync(JsonSerializer.Serialize(response));
}
#endregion
@@ -1,7 +1,7 @@
using System;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services;
@@ -43,7 +43,7 @@ public interface IWebServerService : IArtemisService
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
/// </summary>
@@ -51,8 +51,9 @@ public interface IWebServerService : IArtemisService
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
[Obsolete("This way of updating is too unpredictable in combination with nested events, use AddJsonEndPoint<T> to update manually instead")]
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new();
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary>
@@ -3,12 +3,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using EmbedIO;
using EmbedIO.WebApi;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services;
@@ -22,6 +23,7 @@ internal class WebServerService : IWebServerService, IDisposable
private readonly PluginSetting<bool> _webServerEnabledSetting;
private readonly PluginSetting<int> _webServerPortSetting;
private readonly object _webserverLock = new();
private readonly JsonSerializerOptions _jsonOptions = new(CoreJson.GetJsonSerializerOptions()) {ReferenceHandler = ReferenceHandler.IgnoreCycles, WriteIndented = true};
private CancellationTokenSource? _cts;
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
@@ -120,7 +122,7 @@ internal class WebServerService : IWebServerService, IDisposable
Server = null;
}
WebApiModule apiModule = new("/", JsonNetSerializer);
WebApiModule apiModule = new("/", SystemTextJsonSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager()
@@ -138,7 +140,7 @@ internal class WebServerService : IWebServerService, IDisposable
// Add registered controllers to the API module
foreach (WebApiControllerRegistration registration in _controllers)
apiModule.RegisterController(registration.ControllerType, (Func<WebApiController>) registration.UntypedFactory);
// Listen for state changes.
server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState);
@@ -173,7 +175,7 @@ internal class WebServerService : IWebServerService, IDisposable
OnWebServerStarted();
}
}
private void AutoStartWebServer()
{
try
@@ -240,6 +242,7 @@ internal class WebServerService : IWebServerService, IDisposable
return endPoint;
}
[Obsolete("Use AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler) instead")]
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new()
{
if (module == null) throw new ArgumentNullException(nameof(module));
@@ -316,7 +319,7 @@ internal class WebServerService : IWebServerService, IDisposable
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string response = JsonConvert.SerializeObject(new Dictionary<string, object?>
string response = CoreJson.Serialize(new Dictionary<string, object?>
{
{"StatusCode", context.Response.StatusCode},
{"StackTrace", exception.StackTrace},
@@ -331,17 +334,16 @@ internal class WebServerService : IWebServerService, IDisposable
await writer.WriteAsync(response);
}
private async Task JsonNetSerializer(IHttpContext context, object? data)
private async Task SystemTextJsonSerializer(IHttpContext context, object? data)
{
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string json = JsonConvert.SerializeObject(data, new JsonSerializerSettings {PreserveReferencesHandling = PreserveReferencesHandling.Objects});
await writer.WriteAsync(json);
await writer.WriteAsync(JsonSerializer.Serialize(data, _jsonOptions));
}
private async Task HandleHttpExceptionJson(IHttpContext context, IHttpException httpException)
{
await context.SendStringAsync(JsonConvert.SerializeObject(httpException, Formatting.Indented), MimeType.Json, Encoding.UTF8);
await context.SendStringAsync(JsonSerializer.Serialize(httpException, _jsonOptions), MimeType.Json, Encoding.UTF8);
}
#endregion