diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj
index e81dc0d64..75fa9a3a1 100644
--- a/src/Artemis.Core/Artemis.Core.csproj
+++ b/src/Artemis.Core/Artemis.Core.csproj
@@ -42,7 +42,6 @@
-
@@ -50,6 +49,7 @@
+
diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs
index ce374ca88..c61aa021c 100644
--- a/src/Artemis.Core/Constants.cs
+++ b/src/Artemis.Core/Constants.cs
@@ -4,10 +4,8 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text.Json;
using Artemis.Core.JsonConverters;
-using Artemis.Core.Services;
-using Artemis.Core.SkiaSharp;
-using Newtonsoft.Json;
namespace Artemis.Core;
@@ -62,7 +60,7 @@ public static class Constants
/// The full path to the Artemis user layouts folder
///
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
-
+
///
/// The full path to the Artemis user layouts folder
///
@@ -94,20 +92,6 @@ public static class Constants
///
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
- internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
- internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
-
- internal static JsonSerializerSettings JsonConvertSettings = new()
- {
- Converters = new List {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
- };
-
- internal static JsonSerializerSettings JsonConvertTypedSettings = new()
- {
- TypeNameHandling = TypeNameHandling.All,
- Converters = new List {new SKColorConverter(), new NumericJsonConverter(), new ForgivingIntConverter()}
- };
-
///
/// A read-only collection containing all primitive numeric types
///
@@ -155,4 +139,8 @@ public static class Constants
/// Gets the startup arguments provided to the application
///
public static ReadOnlyCollection StartupArguments { get; set; } = null!;
+
+ internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
+ internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
+ internal static readonly JsonSerializerOptions JsonConvertSettings = new() {Converters = {new SKColorConverter(), new NumericJsonConverter()}};
}
\ No newline at end of file
diff --git a/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs b/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs
deleted file mode 100644
index c43fa7bf8..000000000
--- a/src/Artemis.Core/JsonConverters/ForgivingIntConverter.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-
-namespace Artemis.Core.JsonConverters;
-
-///
-/// An int converter that, if required, will round float values
-///
-internal class ForgivingIntConverter : JsonConverter
-{
- public override bool CanWrite => false;
-
- public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
- {
- throw new NotImplementedException();
- }
-
- public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
- {
- JValue? jsonValue = serializer.Deserialize(reader);
- if (jsonValue == null)
- throw new JsonReaderException("Failed to deserialize forgiving int value");
-
- if (jsonValue.Type == JTokenType.Float)
- return (int) Math.Round(jsonValue.Value());
- if (jsonValue.Type == JTokenType.Integer)
- return jsonValue.Value();
-
- throw new JsonReaderException("Failed to deserialize forgiving int value");
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/JsonConverters/ForgivingVersionConverter.cs b/src/Artemis.Core/JsonConverters/ForgivingVersionConverter.cs
deleted file mode 100644
index 8a325f7e0..000000000
--- a/src/Artemis.Core/JsonConverters/ForgivingVersionConverter.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
-using System;
-
-namespace Artemis.Core.JsonConverters
-{
- ///
- /// Version converter that is forgiving of missing parts of the version string,
- /// setting them to zero instead of -1.
- ///
- internal class ForgivingVersionConverter : VersionConverter
- {
- public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
- {
- object? obj = base.ReadJson(reader, objectType, existingValue, serializer);
- if (obj is not Version v)
- return obj;
-
- int major = v.Major == -1 ? 0 : v.Major;
- int minor = v.Minor == -1 ? 0 : v.Minor;
- int build = v.Build == -1 ? 0 : v.Build;
- int revision = v.Revision == -1 ? 0 : v.Revision;
- return new Version(major, minor, build, revision);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs b/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
index 0dea83b5e..3d018c4c2 100644
--- a/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
+++ b/src/Artemis.Core/JsonConverters/NumericJsonConverter.cs
@@ -1,24 +1,26 @@
using System;
-using Newtonsoft.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
-namespace Artemis.Core.JsonConverters;
-
-internal class NumericJsonConverter : JsonConverter
+namespace Artemis.Core.JsonConverters
{
- #region Overrides of JsonConverter
-
- ///
- public override void WriteJson(JsonWriter writer, Numeric value, JsonSerializer serializer)
+ internal class NumericJsonConverter : JsonConverter
{
- float floatValue = value;
- writer.WriteValue(floatValue);
- }
+ public override void Write(Utf8JsonWriter writer, Numeric value, JsonSerializerOptions options)
+ {
+ float floatValue = value;
+ writer.WriteNumberValue(floatValue);
+ }
- ///
- public override Numeric ReadJson(JsonReader reader, Type objectType, Numeric existingValue, bool hasExistingValue, JsonSerializer serializer)
- {
- return new Numeric(reader.Value);
- }
+ public override Numeric Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.Number)
+ {
+ throw new JsonException($"Expected a number token, but got {reader.TokenType}.");
+ }
- #endregion
+ float floatValue = reader.GetSingle();
+ return new Numeric(floatValue);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.Core/JsonConverters/SKColorConverter.cs b/src/Artemis.Core/JsonConverters/SKColorConverter.cs
index 40912c153..b2818d754 100644
--- a/src/Artemis.Core/JsonConverters/SKColorConverter.cs
+++ b/src/Artemis.Core/JsonConverters/SKColorConverter.cs
@@ -1,21 +1,26 @@
using System;
-using Newtonsoft.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using SkiaSharp;
-namespace Artemis.Core.JsonConverters;
-
-internal class SKColorConverter : JsonConverter
+namespace Artemis.Core.JsonConverters
{
- public override void WriteJson(JsonWriter writer, SKColor value, JsonSerializer serializer)
+ internal class SKColorConverter : JsonConverter
{
- writer.WriteValue(value.ToString());
- }
+ public override void Write(Utf8JsonWriter writer, SKColor value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
- public override SKColor ReadJson(JsonReader reader, Type objectType, SKColor existingValue, bool hasExistingValue, JsonSerializer serializer)
- {
- if (reader.Value is string value && !string.IsNullOrWhiteSpace(value))
- return SKColor.Parse(value);
+ public override SKColor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ {
+ throw new JsonException($"Expected a string token, but got {reader.TokenType}.");
+ }
- return SKColor.Empty;
+ string colorString = reader.GetString() ?? string.Empty;
+ return SKColor.TryParse(colorString, out SKColor color) ? color : SKColor.Empty;
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/JsonConverters/StreamConverter.cs b/src/Artemis.Core/JsonConverters/StreamConverter.cs
deleted file mode 100644
index 7ce3529ee..000000000
--- a/src/Artemis.Core/JsonConverters/StreamConverter.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.IO;
-using Newtonsoft.Json;
-
-namespace Artemis.Core.JsonConverters;
-
-///
-public class StreamConverter : JsonConverter
-{
- #region Overrides of JsonConverter
-
- ///
- public override void WriteJson(JsonWriter writer, Stream? value, JsonSerializer serializer)
- {
- if (value == null)
- {
- writer.WriteNull();
- return;
- }
-
- using MemoryStream memoryStream = new();
- value.Position = 0;
- value.CopyTo(memoryStream);
- writer.WriteValue(memoryStream.ToArray());
- }
-
- ///
- public override Stream? ReadJson(JsonReader reader, Type objectType, Stream? existingValue, bool hasExistingValue, JsonSerializer serializer)
- {
- if (reader.Value is not string base64)
- return null;
-
- if (existingValue == null || !hasExistingValue || !existingValue.CanRead)
- return new MemoryStream(Convert.FromBase64String(base64));
-
- using MemoryStream memoryStream = new(Convert.FromBase64String(base64));
- existingValue.Position = 0;
- memoryStream.CopyTo(existingValue);
- existingValue.Position = 0;
- return existingValue;
- }
-
- #endregion
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs
index ec3496955..2db77c92b 100644
--- a/src/Artemis.Core/Models/Profile/Folder.cs
+++ b/src/Artemis.Core/Models/Profile/Folder.cs
@@ -164,7 +164,7 @@ public Folder CreateCopy()
if (Parent == null)
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
- FolderEntity entityCopy = CoreJson.DeserializeObject(CoreJson.SerializeObject(FolderEntity, true), true)!;
+ FolderEntity entityCopy = CoreJson.Deserialize(CoreJson.Serialize(FolderEntity))!;
entityCopy.Id = Guid.NewGuid();
entityCopy.Name += " - Copy";
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index ff9a7d514..5ecfeeca6 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
+using System.Text.Json;
using Artemis.Storage.Entities.Profile;
-using Newtonsoft.Json;
namespace Artemis.Core;
@@ -324,8 +324,8 @@ public void ApplyDefaultValue()
// Reference types make a deep clone (ab)using JSON
else
{
- string json = CoreJson.SerializeObject(DefaultValue, true);
- SetCurrentValue(CoreJson.DeserializeObject(json)!);
+ string json = CoreJson.Serialize(DefaultValue);
+ SetCurrentValue(CoreJson.Deserialize(json)!);
}
}
@@ -420,7 +420,7 @@ public void AddKeyframe(LayerPropertyKeyframe keyframe)
try
{
- T? value = CoreJson.DeserializeObject(keyframeEntity.Value);
+ T? value = CoreJson.Deserialize(keyframeEntity.Value);
if (value == null)
return null;
@@ -625,7 +625,7 @@ public void Load()
try
{
if (Entity.Value != null)
- BaseValue = CoreJson.DeserializeObject(Entity.Value)!;
+ BaseValue = CoreJson.Deserialize(Entity.Value)!;
}
catch (JsonException)
{
@@ -664,7 +664,7 @@ public void Save()
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
- Entity.Value = CoreJson.SerializeObject(BaseValue);
+ Entity.Value = CoreJson.Serialize(BaseValue);
Entity.KeyframesEnabled = KeyframesEnabled;
Entity.KeyframeEntities.Clear();
Entity.KeyframeEntities.AddRange(Keyframes.Select(k => k.GetKeyframeEntity()));
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
index b6f37c9ea..b5ceaa139 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
@@ -69,7 +69,7 @@ public KeyframeEntity GetKeyframeEntity()
{
return new KeyframeEntity
{
- Value = CoreJson.SerializeObject(Value),
+ Value = CoreJson.Serialize(Value),
Position = Position,
EasingFunction = (int) EasingFunction
};
diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationExportModel.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationExportModel.cs
deleted file mode 100644
index 075c5f54b..000000000
--- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationExportModel.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.IO;
-using Artemis.Core.JsonConverters;
-using Artemis.Storage.Entities.Profile;
-using Newtonsoft.Json;
-
-namespace Artemis.Core;
-
-///
-/// A model that can be used to serialize a profile configuration, it's profile and it's icon
-///
-public class ProfileConfigurationExportModel : IDisposable
-{
- ///
- /// Gets or sets the storage entity of the profile configuration
- ///
- public ProfileConfigurationEntity? ProfileConfigurationEntity { get; set; }
-
- ///
- /// Gets or sets the storage entity of the profile
- ///
- [JsonProperty(Required = Required.Always)]
- public ProfileEntity ProfileEntity { get; set; } = null!;
-
- ///
- /// Gets or sets a stream containing the profile image
- ///
- [JsonConverter(typeof(StreamConverter))]
- public Stream? ProfileImage { get; set; }
-
- ///
- public void Dispose()
- {
- ProfileImage?.Dispose();
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Modules/DataModel.cs b/src/Artemis.Core/Plugins/Modules/DataModel.cs
index 6d80e3b29..1bdf68094 100644
--- a/src/Artemis.Core/Plugins/Modules/DataModel.cs
+++ b/src/Artemis.Core/Plugins/Modules/DataModel.cs
@@ -4,8 +4,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
+using System.Text.Json.Serialization;
using Humanizer;
-using Newtonsoft.Json;
namespace Artemis.Core.Modules;
diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
index ccd9f4c9f..6c084ff51 100644
--- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
@@ -3,14 +3,12 @@
using System.Linq;
using Artemis.Storage.Entities.Plugins;
using Humanizer;
-using Newtonsoft.Json;
namespace Artemis.Core;
///
/// Represents basic info about a plugin feature and contains a reference to the instance of said feature
///
-[JsonObject(MemberSerialization.OptIn)]
public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
{
private string? _description;
@@ -64,7 +62,6 @@ public Exception? LoadException
///
/// The name of the feature
///
- [JsonProperty(Required = Required.Always)]
public string Name
{
get => _name;
@@ -74,7 +71,6 @@ public string Name
///
/// A short description of the feature
///
- [JsonProperty]
public string? Description
{
get => _description;
@@ -85,7 +81,6 @@ public string? Description
/// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled.
/// Note: always if this is the plugin's only feature
///
- [JsonProperty]
public bool AlwaysEnabled { get; internal set; }
///
diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs
index 91e49eeab..641471345 100644
--- a/src/Artemis.Core/Plugins/PluginInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginInfo.cs
@@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
-using Artemis.Core.JsonConverters;
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
namespace Artemis.Core;
///
/// Represents basic info about a plugin and contains a reference to the instance of said plugin
///
-[JsonObject(MemberSerialization.OptIn)]
public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
{
private Version? _api;
@@ -28,10 +25,11 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
private string _version = null!;
private Uri? _website;
private Uri? _helpPage;
- private bool _hotReloadSupported;
+ private bool _hotReloadSupported = true;
private Uri? _license;
private string? _licenseName;
+ [JsonConstructor]
internal PluginInfo()
{
}
@@ -39,7 +37,8 @@ internal PluginInfo()
///
/// The plugins GUID
///
- [JsonProperty(Required = Required.Always)]
+ [JsonRequired]
+ [JsonInclude]
public Guid Guid
{
get => _guid;
@@ -49,7 +48,8 @@ public Guid Guid
///
/// The name of the plugin
///
- [JsonProperty(Required = Required.Always)]
+ [JsonRequired]
+ [JsonInclude]
public string Name
{
get => _name;
@@ -59,7 +59,6 @@ public string Name
///
/// A short description of the plugin
///
- [JsonProperty]
public string? Description
{
get => _description;
@@ -69,7 +68,6 @@ public string? Description
///
/// Gets or sets the author of this plugin
///
- [JsonProperty]
public string? Author
{
get => _author;
@@ -79,7 +77,6 @@ public string? Author
///
/// Gets or sets the website of this plugin or its author
///
- [JsonProperty]
public Uri? Website
{
get => _website;
@@ -89,7 +86,6 @@ public Uri? Website
///
/// Gets or sets the repository of this plugin
///
- [JsonProperty]
public Uri? Repository
{
get => _repository;
@@ -99,7 +95,6 @@ public Uri? Repository
///
/// Gets or sets the help page of this plugin
///
- [JsonProperty]
public Uri? HelpPage
{
get => _helpPage;
@@ -109,7 +104,6 @@ public Uri? HelpPage
///
/// Gets or sets the help page of this plugin
///
- [JsonProperty]
public Uri? License
{
get => _license;
@@ -119,7 +113,6 @@ public Uri? License
///
/// Gets or sets the author of this plugin
///
- [JsonProperty]
public string? LicenseName
{
get => _licenseName;
@@ -130,7 +123,6 @@ public string? LicenseName
/// The plugins display icon that's shown in the settings see for
/// available icons
///
- [JsonProperty]
public string? Icon
{
get => _icon;
@@ -140,7 +132,8 @@ public string? Icon
///
/// The version of the plugin
///
- [JsonProperty(Required = Required.Always)]
+ [JsonRequired]
+ [JsonInclude]
public string Version
{
get => _version;
@@ -150,7 +143,8 @@ public string Version
///
/// The main entry DLL, should contain a class implementing Plugin
///
- [JsonProperty(Required = Required.Always)]
+ [JsonRequired]
+ [JsonInclude]
public string Main
{
get => _main;
@@ -161,8 +155,6 @@ public string Main
/// Gets or sets a boolean indicating whether this plugin should automatically enable all its features when it is first
/// loaded
///
- [DefaultValue(true)]
- [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool AutoEnableFeatures
{
get => _autoEnableFeatures;
@@ -172,7 +164,7 @@ public bool AutoEnableFeatures
///
/// Gets a boolean indicating whether this plugin requires elevated admin privileges
///
- [JsonProperty]
+ [JsonInclude]
public bool RequiresAdmin
{
get => _requiresAdmin;
@@ -182,8 +174,6 @@ public bool RequiresAdmin
///
/// Gets or sets a boolean indicating whether hot reloading this plugin is supported
///
- [DefaultValue(true)]
- [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool HotReloadSupported
{
get => _hotReloadSupported;
@@ -193,7 +183,7 @@ public bool HotReloadSupported
///
/// Gets
///
- [JsonProperty]
+ [JsonInclude]
public PluginPlatform? Platforms
{
get => _platforms;
@@ -203,8 +193,7 @@ public PluginPlatform? Platforms
///
/// Gets the API version the plugin was built for
///
- [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
- [JsonConverter(typeof(ForgivingVersionConverter))]
+ [JsonInclude]
public Version? Api
{
get => _api;
@@ -214,6 +203,7 @@ public Version? Api
///
/// Gets the plugin this info is associated with
///
+ [JsonIgnore]
public Plugin Plugin
{
get => _plugin;
@@ -223,6 +213,7 @@ public Plugin Plugin
///
/// Gets a string representing either a full path pointing to an svg or the markdown icon
///
+ [JsonIgnore]
public string? ResolvedIcon
{
get
@@ -247,9 +238,11 @@ public override string ToString()
}
///
+ [JsonIgnore]
public List Prerequisites { get; } = new();
///
+ [JsonIgnore]
public IEnumerable PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
///
diff --git a/src/Artemis.Core/Plugins/PluginPlatform.cs b/src/Artemis.Core/Plugins/PluginPlatform.cs
index 6f5ce1c34..bc10a703f 100644
--- a/src/Artemis.Core/Plugins/PluginPlatform.cs
+++ b/src/Artemis.Core/Plugins/PluginPlatform.cs
@@ -1,6 +1,5 @@
using System;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Converters;
+using System.Text.Json.Serialization;
namespace Artemis.Core;
@@ -8,7 +7,7 @@ namespace Artemis.Core;
/// Specifies OS platforms a plugin may support.
///
[Flags]
-[JsonConverter(typeof(StringEnumConverter))]
+[JsonConverter(typeof(JsonStringEnumConverter))]
public enum PluginPlatform
{
/// The Windows platform.
diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs
index cd94e48d6..8f14d542f 100644
--- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs
+++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs
@@ -1,7 +1,7 @@
using System;
+using System.Text.Json;
using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces;
-using Newtonsoft.Json;
namespace Artemis.Core;
@@ -23,9 +23,9 @@ internal PluginSetting(IPluginRepository pluginRepository, PluginSettingEntity p
Name = pluginSettingEntity.Name;
try
{
- _value = CoreJson.DeserializeObject(pluginSettingEntity.Value)!;
+ _value = CoreJson.Deserialize(pluginSettingEntity.Value)!;
}
- catch (JsonReaderException)
+ catch (JsonException)
{
_value = default!;
}
@@ -76,7 +76,7 @@ protected internal virtual void OnSettingSaved()
public string Name { get; }
///
- public bool HasChanged => CoreJson.SerializeObject(Value) != _pluginSettingEntity.Value;
+ public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value;
///
public bool AutoSave { get; set; }
@@ -84,7 +84,7 @@ protected internal virtual void OnSettingSaved()
///
public void RejectChanges()
{
- Value = CoreJson.DeserializeObject(_pluginSettingEntity.Value);
+ Value = CoreJson.Deserialize(_pluginSettingEntity.Value);
}
///
@@ -93,7 +93,7 @@ public void Save()
if (!HasChanged)
return;
- _pluginSettingEntity.Value = CoreJson.SerializeObject(Value);
+ _pluginSettingEntity.Value = CoreJson.Serialize(Value);
_pluginRepository.SaveSetting(_pluginSettingEntity);
OnSettingSaved();
}
diff --git a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs
index 84061e48e..89c1a10ea 100644
--- a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs
+++ b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs
@@ -49,7 +49,7 @@ public PluginSetting GetSetting(string name, T? defaultValue = default)
{
Name = name,
PluginGuid = Plugin.Guid,
- Value = CoreJson.SerializeObject(defaultValue)
+ Value = CoreJson.Serialize(defaultValue)
};
_pluginRepository.AddSetting(settingEntity);
}
diff --git a/src/Artemis.Core/Services/Core/BuildInfo.cs b/src/Artemis.Core/Services/Core/BuildInfo.cs
deleted file mode 100644
index 2c694d67a..000000000
--- a/src/Artemis.Core/Services/Core/BuildInfo.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Globalization;
-using Newtonsoft.Json;
-
-namespace Artemis.Core.Services.Core;
-
-///
-/// Represents build information related to the currently running Artemis build
-///
-public class BuildInfo
-{
- ///
- /// Gets the unique ID of this build
- ///
- [JsonProperty]
- public int BuildId { get; internal set; }
-
- ///
- /// Gets the build number. This contains the date and the build count for that day.
- /// Per example 20210108.4
- ///
- [JsonProperty]
- public double BuildNumber { get; internal set; }
-
- ///
- /// Gets the build number formatted as a string. This contains the date and the build count for that day.
- /// Per example 20210108.4
- ///
- public string BuildNumberDisplay => BuildNumber.ToString(CultureInfo.InvariantCulture);
-
- ///
- /// Gets the branch of the triggering repo the build was created for.
- ///
- [JsonProperty]
- public string SourceBranch { get; internal set; } = null!;
-
- ///
- /// Gets the commit ID used to create this build
- ///
- [JsonProperty]
- public string SourceVersion { get; internal set; } = null!;
-
- ///
- /// Gets a boolean indicating whether the current build is a local build
- ///
- public bool IsLocalBuild { get; internal set; }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs
index 1050484c0..e72ea6882 100644
--- a/src/Artemis.Core/Services/ModuleService.cs
+++ b/src/Artemis.Core/Services/ModuleService.cs
@@ -5,7 +5,6 @@
using System.Threading.Tasks;
using System.Timers;
using Artemis.Core.Modules;
-using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services;
diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs
index 96a0b617b..79f118749 100644
--- a/src/Artemis.Core/Services/NodeService.cs
+++ b/src/Artemis.Core/Services/NodeService.cs
@@ -4,7 +4,6 @@
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 @@ public TypeColorRegistration GetTypeColorRegistration(Type type)
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(json);
+ NodeScriptEntity? entity = CoreJson.Deserialize(json);
if (entity == null)
throw new ArtemisCoreException("Failed to load node script");
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 75cd31cb6..67e3083e4 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -127,7 +127,7 @@ private void ExtractBuiltInPlugin(FileInfo zipFile, DirectoryInfo pluginDirector
throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName);
using StreamReader reader = new(metaDataFileEntry.Open());
- PluginInfo builtInPluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!;
+ PluginInfo builtInPluginInfo = CoreJson.Deserialize(reader.ReadToEnd())!;
string preferred = builtInPluginInfo.PreferredPluginDirectory;
// Find the matching plugin in the plugin folder
@@ -360,7 +360,7 @@ public void UnloadPlugins()
_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(File.ReadAllText(metadataFile))!;
+ PluginInfo pluginInfo = CoreJson.Deserialize(File.ReadAllText(metadataFile))!;
if (!pluginInfo.IsCompatible)
return null;
@@ -610,7 +610,7 @@ public void DisablePlugin(Plugin plugin, bool saveState)
throw new ArtemisPluginException("Couldn't find a plugin.json in " + fileName);
using StreamReader reader = new(metaDataFileEntry.Open());
- PluginInfo pluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!;
+ PluginInfo pluginInfo = CoreJson.Deserialize(reader.ReadToEnd())!;
if (!pluginInfo.Main.EndsWith(".dll"))
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
index 4e8d357fa..2d2dfae7c 100644
--- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
@@ -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;
///
public interface IProfileService : IArtemisService
{
- ///
- /// Gets the JSON serializer settings used to import/export profiles.
- ///
- public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
-
///
/// Gets a read only collection containing all the profile categories.
///
diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs
index d9f50cd1a..5cafbd7db 100644
--- a/src/Artemis.Core/Services/Storage/ProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/ProfileService.cs
@@ -5,13 +5,13 @@
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 @@ public async Task ExportProfile(ProfileConfiguration profileConfiguratio
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 @@ public async Task ImportProfile(Stream archiveStream, Prof
// Deserialize profile configuration to JObject
await using Stream configurationStream = configurationEntry.Open();
using StreamReader configurationReader = new(configurationStream);
- JObject? configurationJson = JsonConvert.DeserializeObject(await configurationReader.ReadToEndAsync(), IProfileService.ExportSettings);
+ JsonObject? configurationJson = CoreJson.Deserialize(await configurationReader.ReadToEndAsync());
// Deserialize profile to JObject
await using Stream profileStream = profileEntry.Open();
using StreamReader profileReader = new(profileStream);
- JObject? profileJson = JsonConvert.DeserializeObject(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings);
+ JsonObject? profileJson = CoreJson.Deserialize(await profileReader.ReadToEndAsync());
// Before deserializing, apply any pending migrations
MigrateProfile(configurationJson, profileJson);
// Deserialize profile configuration to ProfileConfigurationEntity
- ProfileConfigurationEntity? configurationEntity = configurationJson?.ToObject(JsonSerializer.Create(IProfileService.ExportSettings));
+ ProfileConfigurationEntity? configurationEntity = configurationJson?.Deserialize(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(JsonSerializer.Create(IProfileService.ExportSettings));
+ ProfileEntity? profileEntity = profileJson?.Deserialize(Constants.JsonConvertSettings);
if (profileEntity == null)
throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json");
@@ -559,7 +559,7 @@ private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEvent
}
}
- private void MigrateProfile(JObject? configurationJson, JObject? profileJson)
+ private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson)
{
if (configurationJson == null || profileJson == null)
return;
@@ -568,7 +568,7 @@ private void MigrateProfile(JObject? configurationJson, JObject? profileJson)
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
{
- if (profileMigrator.Version <= configurationJson["Version"]!.Value())
+ if (profileMigrator.Version <= configurationJson["Version"]!.GetValue())
continue;
profileMigrator.Migrate(configurationJson, profileJson);
diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs
index e9aa58ad1..438a638fc 100644
--- a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs
+++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs
@@ -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 : PluginEndPoint where T : DataModel, new()
{
private readonly Module _module;
+ private readonly Action _update;
internal DataModelJsonPluginEndPoint(Module 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 @@ internal DataModelJsonPluginEndPoint(Module module, string name, PluginsModul
///
public bool ThrowOnFail { get; set; }
- #region Overrides of PluginEndPoint
-
///
protected override async Task ProcessRequest(IHttpContext context)
{
@@ -44,7 +48,9 @@ protected override async Task ProcessRequest(IHttpContext context)
using TextReader reader = context.OpenRequestText();
try
{
- JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
+ T? dataModel = CoreJson.Deserialize(await reader.ReadToEndAsync());
+ if (dataModel != null)
+ _update(dataModel, _module.DataModel);
}
catch (JsonException)
{
@@ -53,5 +59,26 @@ protected override async Task ProcessRequest(IHttpContext context)
}
}
- #endregion
+ private Action CreateUpdateAction()
+ {
+ ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
+ ParameterExpression targetParameter = Expression.Parameter(typeof(T), "target");
+
+ IEnumerable 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>(body, sourceParameter, targetParameter).Compile();
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/JsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/JsonPluginEndPoint.cs
index 355a233ec..2fc5c6977 100644
--- a/src/Artemis.Core/Services/WebServer/EndPoints/JsonPluginEndPoint.cs
+++ b/src/Artemis.Core/Services/WebServer/EndPoints/JsonPluginEndPoint.cs
@@ -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 @@ protected override async Task ProcessRequest(IHttpContext context)
object? response = null;
try
{
- T? deserialized = JsonConvert.DeserializeObject(await reader.ReadToEndAsync());
+ T? deserialized = JsonSerializer.Deserialize(await reader.ReadToEndAsync());
if (deserialized == null)
throw new JsonException("Deserialization returned null");
@@ -74,7 +74,7 @@ protected override async Task ProcessRequest(IHttpContext context)
}
await using TextWriter writer = context.OpenResponseText();
- await writer.WriteAsync(JsonConvert.SerializeObject(response));
+ await writer.WriteAsync(JsonSerializer.Serialize(response));
}
#endregion
diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs
index 55cbbd3d9..fed0e033a 100644
--- a/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs
+++ b/src/Artemis.Core/Services/WebServer/EndPoints/PluginEndPoint.cs
@@ -1,7 +1,7 @@
using System;
+using System.Text.Json.Serialization;
using System.Threading.Tasks;
using EmbedIO;
-using Newtonsoft.Json;
namespace Artemis.Core.Services;
diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs
index d185d33c9..e65d626fe 100644
--- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs
+++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs
@@ -43,7 +43,7 @@ public interface IWebServerService : IArtemisService
///
/// The resulting end point
JsonPluginEndPoint AddResponsiveJsonEndPoint(PluginFeature feature, string endPointName, Func requestHandler);
-
+
///
/// Adds a new endpoint that directly maps received JSON to the data model of the provided .
///
@@ -51,8 +51,9 @@ public interface IWebServerService : IArtemisService
/// The module whose datamodel to apply the received JSON to
/// The name of the end point, must be unique
/// The resulting end point
+ [Obsolete("This way of updating is too unpredictable in combination with nested events, use AddJsonEndPoint to update manually instead")]
DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel, new();
-
+
///
/// Adds a new endpoint for the given plugin feature receiving an a .
///
diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs
index 263d95315..e0ab36288 100644
--- a/src/Artemis.Core/Services/WebServer/WebServerService.cs
+++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs
@@ -3,12 +3,13 @@
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 _webServerEnabledSetting;
private readonly PluginSetting _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 @@ private WebServer CreateWebServer()
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 @@ private WebServer CreateWebServer()
// Add registered controllers to the API module
foreach (WebApiControllerRegistration registration in _controllers)
apiModule.RegisterController(registration.ControllerType, (Func) registration.UntypedFactory);
-
+
// Listen for state changes.
server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState);
@@ -173,7 +175,7 @@ private void StartWebServer()
OnWebServerStarted();
}
}
-
+
private void AutoStartWebServer()
{
try
@@ -240,6 +242,7 @@ public RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointNa
return endPoint;
}
+ [Obsolete("Use AddJsonEndPoint(PluginFeature feature, string endPointName, Action requestHandler) instead")]
public DataModelJsonPluginEndPoint AddDataModelJsonEndPoint(Module module, string endPointName) where T : DataModel, new()
{
if (module == null) throw new ArgumentNullException(nameof(module));
@@ -316,7 +319,7 @@ private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
- string response = JsonConvert.SerializeObject(new Dictionary
+ string response = CoreJson.Serialize(new Dictionary
{
{"StatusCode", context.Response.StatusCode},
{"StackTrace", exception.StackTrace},
@@ -331,17 +334,16 @@ private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception
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
diff --git a/src/Artemis.Core/Utilities/CoreJson.cs b/src/Artemis.Core/Utilities/CoreJson.cs
index 1cb870e61..fa9477073 100644
--- a/src/Artemis.Core/Utilities/CoreJson.cs
+++ b/src/Artemis.Core/Utilities/CoreJson.cs
@@ -1,7 +1,6 @@
-using System;
-using System.Diagnostics;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using Newtonsoft.Json;
+using System.Text.Json;
namespace Artemis.Core;
@@ -10,62 +9,36 @@ namespace Artemis.Core;
///
public static class CoreJson
{
- #region Serialize
-
///
/// Serializes the specified object to a JSON string.
///
/// The object to serialize.
- /// If set to true sets TypeNameHandling to
/// A JSON string representation of the object.
[DebuggerStepThrough]
- public static string SerializeObject(object? value, bool handleTypeNames = false)
- {
- return JsonConvert.SerializeObject(value, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
- }
-
- #endregion
-
- #region Deserialize
-
- ///
- /// Deserializes the JSON to a .NET object.
- ///
- /// The JSON to deserialize.
- /// If set to true sets TypeNameHandling to
- /// The deserialized object from the JSON string.
- [DebuggerStepThrough]
- public static object? DeserializeObject(string value, bool handleTypeNames = false)
+ public static string Serialize(object? value)
{
- return JsonConvert.DeserializeObject(value, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
+ return JsonSerializer.Serialize(value, Constants.JsonConvertSettings);
}
-
+
///
/// Deserializes the JSON to the specified .NET type.
///
+ /// The type of the object to deserialize to.
/// The JSON to deserialize.
- /// The of object being deserialized.
- /// If set to true sets TypeNameHandling to
/// The deserialized object from the JSON string.
[DebuggerStepThrough]
- public static object? DeserializeObject(string value, Type type, bool handleTypeNames = false)
+ [return: MaybeNull]
+ public static T Deserialize(string value)
{
- return JsonConvert.DeserializeObject(value, type, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
+ return JsonSerializer.Deserialize(value, Constants.JsonConvertSettings);
}
-
+
///
- /// Deserializes the JSON to the specified .NET type.
+ /// Gets a copy of the JSON serializer options used by Artemis Core
///
- /// The type of the object to deserialize to.
- /// The JSON to deserialize.
- /// If set to true sets TypeNameHandling to
- /// The deserialized object from the JSON string.
- [DebuggerStepThrough]
- [return: MaybeNull]
- public static T DeserializeObject(string value, bool handleTypeNames = false)
+ /// A copy of the JSON serializer options used by Artemis Core
+ public static JsonSerializerOptions GetJsonSerializerOptions()
{
- return JsonConvert.DeserializeObject(value, handleTypeNames ? Constants.JsonConvertTypedSettings : Constants.JsonConvertSettings);
+ return new JsonSerializerOptions(Constants.JsonConvertSettings);
}
-
- #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs
index 81d9e7eac..3d091a4b1 100644
--- a/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs
+++ b/src/Artemis.Core/VisualScripting/Nodes/NodeTStorage.cs
@@ -41,12 +41,12 @@ public TStorage? Storage
///
public override string SerializeStorage()
{
- return CoreJson.SerializeObject(Storage, true);
+ return CoreJson.Serialize(Storage);
}
///
public override void DeserializeStorage(string serialized)
{
- Storage = CoreJson.DeserializeObject(serialized) ?? default(TStorage);
+ Storage = CoreJson.Deserialize(serialized) ?? default(TStorage);
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/VisualScripting/Pins/InputPin.cs b/src/Artemis.Core/VisualScripting/Pins/InputPin.cs
index 8addc4627..a183885c5 100644
--- a/src/Artemis.Core/VisualScripting/Pins/InputPin.cs
+++ b/src/Artemis.Core/VisualScripting/Pins/InputPin.cs
@@ -1,5 +1,4 @@
using System;
-using Newtonsoft.Json;
namespace Artemis.Core;
@@ -10,7 +9,6 @@ public sealed class InputPin : Pin
{
#region Constructors
- [JsonConstructor]
internal InputPin(INode node, string name)
: base(node, name)
{
diff --git a/src/Artemis.Core/VisualScripting/Pins/OutputPin.cs b/src/Artemis.Core/VisualScripting/Pins/OutputPin.cs
index 52bd0993a..8397eddf6 100644
--- a/src/Artemis.Core/VisualScripting/Pins/OutputPin.cs
+++ b/src/Artemis.Core/VisualScripting/Pins/OutputPin.cs
@@ -1,5 +1,4 @@
using System;
-using Newtonsoft.Json;
namespace Artemis.Core;
@@ -10,7 +9,6 @@ public sealed class OutputPin : Pin
{
#region Constructors
- [JsonConstructor]
internal OutputPin(INode node, string name)
: base(node, name)
{
diff --git a/src/Artemis.Storage/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj
index cc0ac235f..b01c7ef62 100644
--- a/src/Artemis.Storage/Artemis.Storage.csproj
+++ b/src/Artemis.Storage/Artemis.Storage.csproj
@@ -9,6 +9,6 @@
-
+
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs b/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs
index 57c914786..56444bd86 100644
--- a/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs
@@ -1,3 +1,9 @@
-namespace Artemis.Storage.Entities.Profile.AdaptionHints;
+using System.Text.Json.Serialization;
+namespace Artemis.Storage.Entities.Profile.AdaptionHints;
+
+[JsonDerivedType(typeof(CategoryAdaptionHintEntity), "Category")]
+[JsonDerivedType(typeof(DeviceAdaptionHintEntity), "Device")]
+[JsonDerivedType(typeof(KeyboardSectionAdaptionHintEntity), "KeyboardSection")]
+[JsonDerivedType(typeof(SingleLedAdaptionHintEntity), "SingleLed")]
public interface IAdaptionHintEntity;
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs
index f1f24ee14..93fc27834 100644
--- a/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/Conditions/IConditionEntity.cs
@@ -1,3 +1,10 @@
-namespace Artemis.Storage.Entities.Profile.Abstract;
+using System.Text.Json.Serialization;
+using Artemis.Storage.Entities.Profile.Conditions;
+namespace Artemis.Storage.Entities.Profile.Abstract;
+
+[JsonDerivedType(typeof(AlwaysOnConditionEntity), "AlwaysOn")]
+[JsonDerivedType(typeof(EventConditionEntity), "Event")]
+[JsonDerivedType(typeof(PlayOnceConditionEntity), "PlayOnce")]
+[JsonDerivedType(typeof(StaticConditionEntity), "Static")]
public interface IConditionEntity;
\ No newline at end of file
diff --git a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs
index a3eeef113..aa20adf7f 100644
--- a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs
@@ -1,5 +1,6 @@
using System;
using Artemis.Storage.Entities.Profile.Nodes;
+using Serilog.Core;
namespace Artemis.Storage.Entities.Profile;
@@ -26,5 +27,5 @@ public class ProfileConfigurationEntity
public Guid ProfileId { get; set; }
public bool FadeInAndOut { get; set; }
- public int Version { get; set; }
+ public int Version { get; set; } = StorageMigrationService.PROFILE_VERSION;
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Migrations/IProfileMigration.cs b/src/Artemis.Storage/Migrations/IProfileMigration.cs
index c4fb710ec..d0e1b2126 100644
--- a/src/Artemis.Storage/Migrations/IProfileMigration.cs
+++ b/src/Artemis.Storage/Migrations/IProfileMigration.cs
@@ -1,9 +1,9 @@
-using Newtonsoft.Json.Linq;
+using System.Text.Json.Nodes;
namespace Artemis.Storage.Migrations;
public interface IProfileMigration
{
int Version { get; }
- void Migrate(JObject configurationJson, JObject profileJson);
+ void Migrate(JsonObject configurationJson, JsonObject profileJson);
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs b/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs
index a1006bab8..701ab91ee 100644
--- a/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs
+++ b/src/Artemis.Storage/Migrations/Profile/M0001NodeProviders.cs
@@ -1,88 +1,91 @@
-using Newtonsoft.Json.Linq;
+using System.Text.Json.Nodes;
-namespace Artemis.Storage.Migrations.Profile;
-
-///
-/// Migrates nodes to be provider-based.
-/// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin.
-///
-internal class M0001NodeProviders : IProfileMigration
+namespace Artemis.Storage.Migrations.Profile
{
- ///
- public int Version => 1;
-
- ///
- public void Migrate(JObject configurationJson, JObject profileJson)
+ ///
+ /// Migrates nodes to be provider-based.
+ /// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin.
+ ///
+ internal class M0001NodeProviders : IProfileMigration
{
- JArray? folders = (JArray?) profileJson["Folders"]?["$values"];
- JArray? layers = (JArray?) profileJson["Layers"]?["$values"];
+ ///
+ public int Version => 1;
- if (folders != null)
+ ///
+ public void Migrate(JsonObject configurationJson, JsonObject profileJson)
{
- foreach (JToken folder in folders)
- MigrateProfileElement(folder);
- }
+ JsonArray? folders = profileJson["Folders"]?["$values"]?.AsArray();
+ JsonArray? layers = profileJson["Layers"]?["$values"]?.AsArray();
- if (layers != null)
- {
- foreach (JToken layer in layers)
+ if (folders != null)
{
- MigrateProfileElement(layer);
- MigratePropertyGroup(layer["GeneralPropertyGroup"]);
- MigratePropertyGroup(layer["TransformPropertyGroup"]);
- MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]);
+ foreach (JsonNode? folder in folders)
+ MigrateProfileElement(folder);
}
- }
- }
- private void MigrateProfileElement(JToken profileElement)
- {
- JArray? layerEffects = (JArray?) profileElement["LayerEffects"]?["$values"];
- if (layerEffects != null)
- {
- foreach (JToken layerEffect in layerEffects)
- MigratePropertyGroup(layerEffect["PropertyGroup"]);
+ if (layers != null)
+ {
+ foreach (JsonNode? layer in layers)
+ {
+ MigrateProfileElement(layer);
+ MigratePropertyGroup(layer?["GeneralPropertyGroup"]);
+ MigratePropertyGroup(layer?["TransformPropertyGroup"]);
+ MigratePropertyGroup(layer?["LayerBrush"]?["PropertyGroup"]);
+ }
+ }
}
- JToken? displayCondition = profileElement["DisplayCondition"];
- if (displayCondition != null)
- MigrateNodeScript(displayCondition["Script"]);
- }
-
- private void MigratePropertyGroup(JToken? propertyGroup)
- {
- if (propertyGroup == null || !propertyGroup.HasValues)
- return;
-
- JArray? properties = (JArray?) propertyGroup["Properties"]?["$values"];
- JArray? propertyGroups = (JArray?) propertyGroup["PropertyGroups"]?["$values"];
-
- if (properties != null)
+ private void MigrateProfileElement(JsonNode? profileElement)
{
- foreach (JToken property in properties)
- MigrateNodeScript(property["DataBinding"]?["NodeScript"]);
+ if (profileElement == null)
+ return;
+
+ JsonArray? layerEffects = profileElement["LayerEffects"]?["$values"]?.AsArray();
+ if (layerEffects != null)
+ {
+ foreach (JsonNode? layerEffect in layerEffects)
+ MigratePropertyGroup(layerEffect?["PropertyGroup"]);
+ }
+
+ JsonNode? displayCondition = profileElement["DisplayCondition"];
+ if (displayCondition != null)
+ MigrateNodeScript(displayCondition["Script"]);
}
- if (propertyGroups != null)
+ private void MigratePropertyGroup(JsonNode? propertyGroup)
{
- foreach (JToken childPropertyGroup in propertyGroups)
- MigratePropertyGroup(childPropertyGroup);
- }
- }
+ if (propertyGroup == null)
+ return;
- private void MigrateNodeScript(JToken? nodeScript)
- {
- if (nodeScript == null || !nodeScript.HasValues)
- return;
+ JsonArray? properties = propertyGroup["Properties"]?["$values"]?.AsArray();
+ JsonArray? propertyGroups = propertyGroup["PropertyGroups"]?["$values"]?.AsArray();
+ if (properties != null)
+ {
+ foreach (JsonNode? property in properties)
+ MigrateNodeScript(property?["DataBinding"]?["NodeScript"]);
+ }
- JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"];
- if (nodes == null)
- return;
+ if (propertyGroups != null)
+ {
+ foreach (JsonNode? childPropertyGroup in propertyGroups)
+ MigratePropertyGroup(childPropertyGroup);
+ }
+ }
- foreach (JToken node in nodes)
+ private void MigrateNodeScript(JsonNode? nodeScript)
{
- node["Type"] = node["Type"]?.Value()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
- node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
+ JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray();
+ if (nodes == null)
+ return;
+
+ foreach (JsonNode? jsonNode in nodes)
+ {
+ if (jsonNode == null)
+ continue;
+ JsonObject nodeObject = jsonNode.AsObject();
+ nodeObject["Type"] = nodeObject["Type"]?.GetValue().Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
+ nodeObject["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs b/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs
index 9c35a961a..8036092dd 100644
--- a/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs
+++ b/src/Artemis.Storage/Migrations/Profile/M0002NodeProvidersProfileConfig.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json.Linq;
+using System.Text.Json.Nodes;
namespace Artemis.Storage.Migrations.Profile;
@@ -12,24 +12,24 @@ internal class M0002NodeProvidersProfileConfig : IProfileMigration
public int Version => 2;
///
- public void Migrate(JObject configurationJson, JObject profileJson)
+ public void Migrate(JsonObject configurationJson, JsonObject profileJson)
{
MigrateNodeScript(configurationJson["ActivationCondition"]);
}
-
- private void MigrateNodeScript(JToken? nodeScript)
- {
- if (nodeScript == null || !nodeScript.HasValues)
- return;
- JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"];
+ private void MigrateNodeScript(JsonNode? nodeScript)
+ {
+ JsonArray? nodes = nodeScript?["Nodes"]?["$values"]?.AsArray();
if (nodes == null)
return;
- foreach (JToken node in nodes)
+ foreach (JsonNode? jsonNode in nodes)
{
- node["Type"] = node["Type"]?.Value()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
- node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
+ if (jsonNode == null)
+ continue;
+ JsonObject nodeObject = jsonNode.AsObject();
+ nodeObject["Type"] = nodeObject["Type"]?.GetValue().Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes");
+ nodeObject["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78";
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs b/src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs
new file mode 100644
index 000000000..b48cf3b1e
--- /dev/null
+++ b/src/Artemis.Storage/Migrations/Profile/M0003SystemTextJson.cs
@@ -0,0 +1,84 @@
+using System.Linq;
+using System.Text.Json.Nodes;
+
+namespace Artemis.Storage.Migrations.Profile;
+
+///
+/// Migrates profiles to be deserializable by System.Text.Json, removing type information from the JSON arrays and most objects.
+///
+internal class M0003SystemTextJson : IProfileMigration
+{
+ ///
+ public int Version => 3;
+
+ ///
+ public void Migrate(JsonObject configurationJson, JsonObject profileJson)
+ {
+ ConvertToSystemTextJson(configurationJson);
+ ConvertToSystemTextJson(profileJson);
+ }
+
+ private void ConvertToSystemTextJson(JsonObject jsonObject)
+ {
+ FilterType(jsonObject);
+
+ // Recursively convert all JSON arrays from {$type: "xyz", $values: []} to []
+ foreach ((string? key, JsonNode? value) in jsonObject.ToDictionary())
+ {
+ if (value is not JsonObject obj)
+ continue;
+
+ // if there is a $type and a $values, replace the entire node with $values regardless of the value of $type
+ if (obj["$type"] != null && obj["$values"] != null)
+ {
+ JsonArray? values = obj["$values"]?.AsArray();
+ if (values != null)
+ {
+ obj.Remove("$values");
+ jsonObject[key] = values;
+ foreach (JsonNode? jsonNode in values.ToList())
+ {
+ if (jsonNode is JsonObject childObject)
+ ConvertToSystemTextJson(childObject);
+ }
+ }
+
+ obj.Remove("$type");
+ }
+ else
+ {
+ ConvertToSystemTextJson(obj);
+ }
+ }
+ }
+
+ private void FilterType(JsonObject jsonObject)
+ {
+ // Replace or remove $type depending on whether there's a matching JsonDerivedType
+ // This could be done with reflection but that would mean this migration automatically gains new behaviour over time.
+ JsonNode? type = jsonObject["$type"];
+ if (type != null)
+ {
+ // Adaption Hints
+ if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.CategoryAdaptionHintEntity, Artemis.Storage")
+ jsonObject["$type"] = "Category";
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.DeviceAdaptionHintEntity, Artemis.Storage")
+ jsonObject["$type"] = "Device";
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.KeyboardSectionAdaptionHintEntity, Artemis.Storage")
+ jsonObject["$type"] = "KeyboardSection";
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.SingleLedAdaptionHintEntity, Artemis.Storage")
+ jsonObject["$type"] = "SingleLed";
+ // Conditions
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.AlwaysOnConditionEntity, Artemis.Storage")
+ jsonObject["$type"] = "AlwaysOn";
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.EventConditionEntity, Artemis.Storage")
+ jsonObject["$type"] = "Event";
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.PlayOnceConditionEntity, Artemis.Storage")
+ jsonObject["$type"] = "PlayOnce";
+ else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.StaticConditionEntity, Artemis.Storage")
+ jsonObject["$type"] = "Static";
+ else
+ jsonObject.Remove("$type");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs
index 8e1f44ecf..8036b9d7a 100644
--- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs
+++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs
@@ -31,12 +31,24 @@ public void Remove(ProfileCategoryEntity profileCategoryEntity)
public List GetAll()
{
- return _repository.Query().ToList();
+ List categories = _repository.Query().ToList();
+
+ // Update all profile versions to the current version, profile migrations don't apply to LiteDB so anything loadable is assumed to be up to date
+ foreach (ProfileCategoryEntity profileCategoryEntity in categories)
+ UpdateProfileVersions(profileCategoryEntity);
+
+ return categories;
}
public ProfileCategoryEntity? Get(Guid id)
{
- return _repository.FirstOrDefault(p => p.Id == id);
+ ProfileCategoryEntity? result = _repository.FirstOrDefault(p => p.Id == id);
+ if (result == null)
+ return null;
+
+ // Update all profile versions to the current version, profile migrations don't apply to LiteDB so anything loadable is assumed to be up to date
+ UpdateProfileVersions(result);
+ return result;
}
public ProfileCategoryEntity IsUnique(string name, Guid? id)
@@ -72,4 +84,10 @@ public void SaveProfileIconStream(ProfileConfigurationEntity profileConfiguratio
_profileIcons.Upload(profileConfigurationEntity.FileIconId, profileConfigurationEntity.FileIconId + ".png", stream);
}
+
+ private static void UpdateProfileVersions(ProfileCategoryEntity profileCategoryEntity)
+ {
+ foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations)
+ profileConfigurationEntity.Version = StorageMigrationService.PROFILE_VERSION;
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.Storage/StorageMigrationService.cs b/src/Artemis.Storage/StorageMigrationService.cs
index cf728023b..1d726ac8f 100644
--- a/src/Artemis.Storage/StorageMigrationService.cs
+++ b/src/Artemis.Storage/StorageMigrationService.cs
@@ -9,6 +9,8 @@ namespace Artemis.Storage;
public class StorageMigrationService
{
+ public const int PROFILE_VERSION = 3;
+
private readonly ILogger _logger;
private readonly IList _migrations;
private readonly LiteRepository _repository;
diff --git a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs
index 71e06013d..1a7a44c09 100644
--- a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs
+++ b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs
@@ -1,7 +1,8 @@
using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using Artemis.Core;
using Artemis.UI.Shared.DataModelVisualization;
-using Newtonsoft.Json;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
@@ -11,16 +12,15 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
///
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel
\ No newline at end of file