diff --git a/WoWsShipBuilder.Common/Features/Builds/Build.cs b/WoWsShipBuilder.Common/Features/Builds/Build.cs index 1db936dae..8a6011277 100644 --- a/WoWsShipBuilder.Common/Features/Builds/Build.cs +++ b/WoWsShipBuilder.Common/Features/Builds/Build.cs @@ -1,9 +1,11 @@ using System.Globalization; using System.IO.Compression; using System.Security.Cryptography; +using System.Text.Json; +using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using WoWsShipBuilder.DataStructures; +using WoWsShipBuilder.Infrastructure.ApplicationData; using WoWsShipBuilder.Infrastructure.GameData; using WoWsShipBuilder.Infrastructure.Utility; @@ -20,15 +22,15 @@ public class Build [JsonConstructor] private Build(string buildName, string shipIndex, Nation nation, List modules, List upgrades, List consumables, string captain, List skills, List signals, int buildVersion = CurrentBuildVersion) { - BuildName = buildName; - ShipIndex = shipIndex; + BuildName = buildName ?? throw new ArgumentNullException(nameof(buildName)); + ShipIndex = shipIndex ?? throw new ArgumentNullException(nameof(shipIndex)); Nation = nation; - Modules = modules; - Upgrades = upgrades; - Captain = captain; - Skills = skills; - Signals = signals; - Consumables = consumables; + Modules = modules ?? throw new ArgumentNullException(nameof(modules)); + Upgrades = upgrades ?? throw new ArgumentNullException(nameof(upgrades)); + Captain = captain ?? throw new ArgumentNullException(nameof(captain)); + Skills = skills ?? throw new ArgumentNullException(nameof(skills)); + Signals = signals ?? throw new ArgumentNullException(nameof(signals)); + Consumables = consumables ?? throw new ArgumentNullException(nameof(consumables)); BuildVersion = buildVersion; Hash = CreateHash(this); @@ -39,7 +41,6 @@ public Build(string buildName, string shipIndex, Nation nation, List mod { } - [JsonProperty(Required = Required.Always)] public string BuildName { get; } public string ShipIndex { get; } @@ -93,11 +94,11 @@ public static Build CreateBuildFromString(string buildString, ILogger? logger = using var gzip = new DeflateStream(inputStream, CompressionMode.Decompress); using var reader = new StreamReader(gzip, System.Text.Encoding.UTF8); string buildJson = reader.ReadToEnd(); - build = JsonConvert.DeserializeObject(buildJson) ?? throw new InvalidOperationException("Failed to deserialize build object from string"); + build = JsonSerializer.Deserialize(buildJson, AppConstants.JsonSerializerOptions) ?? throw new InvalidOperationException("Failed to deserialize build object from string"); } else if (buildString.StartsWith('{') && buildString.EndsWith('}')) { - build = JsonConvert.DeserializeObject(buildString)!; + build = JsonSerializer.Deserialize(buildString, AppConstants.JsonSerializerOptions)!; } else { @@ -167,7 +168,7 @@ private static Build UpgradeBuild(Build oldBuild, ILogger logger) private static string CreateHash(Build build) { - string buildString = JsonConvert.SerializeObject(build); + string buildString = JsonSerializer.Serialize(build, AppConstants.JsonSerializerOptions); byte[] textData = System.Text.Encoding.UTF8.GetBytes(buildString); byte[] hash = SHA256.HashData(textData); return BitConverter.ToString(hash).Replace("-", string.Empty); @@ -192,7 +193,7 @@ public override bool Equals(object? obj) public string CreateStringFromBuild() { - string buildString = JsonConvert.SerializeObject(this); + string buildString = JsonSerializer.Serialize(this, AppConstants.JsonSerializerOptions); using var output = new MemoryStream(); using (var gzip = new DeflateStream(output, CompressionLevel.Optimal)) { diff --git a/WoWsShipBuilder.Common/Features/Settings/AppSettings.cs b/WoWsShipBuilder.Common/Features/Settings/AppSettings.cs index 9a887531d..f3b9fb51c 100644 --- a/WoWsShipBuilder.Common/Features/Settings/AppSettings.cs +++ b/WoWsShipBuilder.Common/Features/Settings/AppSettings.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; using WoWsShipBuilder.Infrastructure.ApplicationData; using WoWsShipBuilder.Infrastructure.DataTransfer; using ServerType = WoWsShipBuilder.Infrastructure.GameData.ServerType; diff --git a/WoWsShipBuilder.Common/Features/ShipSelection/ShipSelector.razor b/WoWsShipBuilder.Common/Features/ShipSelection/ShipSelector.razor index 5b05a4ed4..208a7c6a1 100644 --- a/WoWsShipBuilder.Common/Features/ShipSelection/ShipSelector.razor +++ b/WoWsShipBuilder.Common/Features/ShipSelection/ShipSelector.razor @@ -5,7 +5,6 @@ @using DynamicData @using Microsoft.AspNetCore.WebUtilities @using Microsoft.Extensions.Options -@using Newtonsoft.Json @using WoWsShipBuilder.Features.Builds.Components @using WoWsShipBuilder.Features.Builds @using WoWsShipBuilder.Features.LinkShortening @@ -17,6 +16,7 @@ @using WoWsShipBuilder.Infrastructure.Metrics @using WoWsShipBuilder.Infrastructure.Utility @using System.Collections.Immutable +@using System.Text.Json @implements IAsyncDisposable @inject ILocalizer Localizer @@ -35,7 +35,7 @@ - + @Localizer.GetAppLocalization(nameof(Translation.WebApp_LoadBuild)).Localization @@ -524,7 +524,7 @@ try { - var build = buildString.StartsWith('{') ? JsonConvert.DeserializeObject(buildString)! : Build.CreateBuildFromString(buildString); + var build = buildString.StartsWith('{') ? JsonSerializer.Deserialize(buildString, AppConstants.JsonSerializerOptions)! : Build.CreateBuildFromString(buildString); AddSavedBuild(Build.UpgradeBuild(build)); buildString = string.Empty; StateHasChanged(); diff --git a/WoWsShipBuilder.Common/Infrastructure/ApplicationData/AppConstants.cs b/WoWsShipBuilder.Common/Infrastructure/ApplicationData/AppConstants.cs index 16d18f329..3c2d22de9 100644 --- a/WoWsShipBuilder.Common/Infrastructure/ApplicationData/AppConstants.cs +++ b/WoWsShipBuilder.Common/Infrastructure/ApplicationData/AppConstants.cs @@ -1,4 +1,6 @@ -using System.Globalization; +using System.Collections.Immutable; +using System.Text.Json; +using System.Text.Json.Serialization; using WoWsShipBuilder.Infrastructure.DataTransfer; namespace WoWsShipBuilder.Infrastructure.ApplicationData; @@ -15,40 +17,26 @@ public static class AppConstants public const string BuildCuratorRoleName = "build-curator"; - // Workaround for webworkers - static AppConstants() - { - try - { - DefaultCultureDetails = new(new("en-GB"), "en"); - SupportedLanguages = new List - { - DefaultCultureDetails, - new(new("zh-CN"), "zh"), - new(new("zh-TW"), "zh_tw"), - new(new("nl-NL"), "nl"), - new(new("fr-FR"), "fr"), - new(new("de-DE"), "de"), - new(new("hu-HU"), "en"), - new(new("it-IT"), "it"), - new(new("ja-JP"), "ja"), - new(new("pt-BR"), "pt_br"), - new(new("ru-RU"), "ru"), - new(new("es-ES"), "es"), - new(new("tr-TR"), "tr"), - }; - } - catch (Exception) - { - DefaultCultureDetails = new(CultureInfo.InvariantCulture, "en"); - SupportedLanguages = new List - { - DefaultCultureDetails, - }; - } - } + public static CultureDetails DefaultCultureDetails { get; } = new(new("en-GB"), "en"); - public static CultureDetails DefaultCultureDetails { get; } + public static IEnumerable SupportedLanguages { get; } = ImmutableArray.Create( + DefaultCultureDetails, + new(new("zh-CN"), "zh"), + new(new("zh-TW"), "zh_tw"), + new(new("nl-NL"), "nl"), + new(new("fr-FR"), "fr"), + new(new("de-DE"), "de"), + new(new("hu-HU"), "en"), + new(new("it-IT"), "it"), + new(new("ja-JP"), "ja"), + new(new("pt-BR"), "pt_br"), + new(new("ru-RU"), "ru"), + new(new("es-ES"), "es"), + new(new("tr-TR"), "tr")); - public static IEnumerable SupportedLanguages { get; } + public static JsonSerializerOptions JsonSerializerOptions { get; } = new() + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; } diff --git a/WoWsShipBuilder.Common/Infrastructure/ApplicationData/DataCacheHelper.cs b/WoWsShipBuilder.Common/Infrastructure/ApplicationData/DataCacheHelper.cs index 6feaccc62..0474fb260 100644 --- a/WoWsShipBuilder.Common/Infrastructure/ApplicationData/DataCacheHelper.cs +++ b/WoWsShipBuilder.Common/Infrastructure/ApplicationData/DataCacheHelper.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using Newtonsoft.Json; +using System.Text.Json; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Aircraft; using WoWsShipBuilder.DataStructures.Captain; @@ -39,7 +39,7 @@ public static async Task AddToCache(string fileName, string category, string con _ => throw new InvalidOperationException(), }; - object? jsonObject = JsonConvert.DeserializeObject(content, type); + object? jsonObject = JsonSerializer.Deserialize(content, type, AppConstants.JsonSerializerOptions); await Semaphore.WaitAsync(); switch (category.ToLowerInvariant()) diff --git a/WoWsShipBuilder.Common/Infrastructure/DataTransfer/CultureDetails.cs b/WoWsShipBuilder.Common/Infrastructure/DataTransfer/CultureDetails.cs index eb38a6015..ac4930f05 100644 --- a/WoWsShipBuilder.Common/Infrastructure/DataTransfer/CultureDetails.cs +++ b/WoWsShipBuilder.Common/Infrastructure/DataTransfer/CultureDetails.cs @@ -1,5 +1,10 @@ using System.Globalization; +using System.Text.Json.Serialization; +using WoWsShipBuilder.Infrastructure.Utility; namespace WoWsShipBuilder.Infrastructure.DataTransfer; -public sealed record CultureDetails(CultureInfo CultureInfo, string LocalizationFileName); +public sealed record CultureDetails( + [property: JsonConverter(typeof(CultureInfoConverter))] + CultureInfo CultureInfo, + string LocalizationFileName); diff --git a/WoWsShipBuilder.Common/Infrastructure/Utility/CultureInfoConverter.cs b/WoWsShipBuilder.Common/Infrastructure/Utility/CultureInfoConverter.cs new file mode 100644 index 000000000..f4e0c6926 --- /dev/null +++ b/WoWsShipBuilder.Common/Infrastructure/Utility/CultureInfoConverter.cs @@ -0,0 +1,18 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace WoWsShipBuilder.Infrastructure.Utility; + +public sealed class CultureInfoConverter : JsonConverter +{ + public override CultureInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return CultureInfo.CreateSpecificCulture(reader.GetString()!); + } + + public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Name); + } +} diff --git a/WoWsShipBuilder.Common/WoWsShipBuilder.Common.csproj b/WoWsShipBuilder.Common/WoWsShipBuilder.Common.csproj index 51085c0a7..455812ad9 100644 --- a/WoWsShipBuilder.Common/WoWsShipBuilder.Common.csproj +++ b/WoWsShipBuilder.Common/WoWsShipBuilder.Common.csproj @@ -24,7 +24,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/WoWsShipBuilder.Common/wwwroot/scripts/settingsHelper.js b/WoWsShipBuilder.Common/wwwroot/scripts/settingsHelper.js index 7fa80c0ac..52dce1fcd 100644 --- a/WoWsShipBuilder.Common/wwwroot/scripts/settingsHelper.js +++ b/WoWsShipBuilder.Common/wwwroot/scripts/settingsHelper.js @@ -1,7 +1,7 @@ export function getAppSettings() { - return window.localStorage['settings']; + return JSON.parse(window.localStorage['settings']); } export function setAppSettings(settings) { - window.localStorage['settings'] = settings; -} \ No newline at end of file + window.localStorage['settings'] = JSON.stringify(settings); +} diff --git a/WoWsShipBuilder.Desktop.Test/DesktopDataServiceTest.cs b/WoWsShipBuilder.Desktop.Test/DesktopDataServiceTest.cs index d4d620feb..8c9cfd62c 100644 --- a/WoWsShipBuilder.Desktop.Test/DesktopDataServiceTest.cs +++ b/WoWsShipBuilder.Desktop.Test/DesktopDataServiceTest.cs @@ -1,8 +1,9 @@ using System.IO.Abstractions.TestingHelpers; +using System.Text.Json; using FluentAssertions; -using Newtonsoft.Json; using WoWsShipBuilder.Desktop.Infrastructure.Data; using WoWsShipBuilder.Features.Settings; +using WoWsShipBuilder.Infrastructure.ApplicationData; namespace WoWsShipBuilder.Desktop.Test; @@ -34,7 +35,7 @@ public void Store_DirectoryNotExisting_DirectoryCreated() mockFileSystem.Directory.Exists(settingsDirectory).Should().BeTrue(); var storedFile = mockFileSystem.File.ReadAllText(settingsPath); - var storedSettings = JsonConvert.DeserializeObject(storedFile); + var storedSettings = JsonSerializer.Deserialize(storedFile, AppConstants.JsonSerializerOptions); storedSettings.Should().BeEquivalentTo(testSettings); } @@ -45,7 +46,7 @@ public async Task Store_FileAlreadyExists_FileReplaced() const string settingsPath = settingsDirectory + @"/settings.json"; const string customDataPath = "1234"; var testSettings = new AppSettings { AutoUpdateEnabled = false, CustomDataPath = customDataPath }; - mockFileSystem.AddFile(settingsPath, new(JsonConvert.SerializeObject(testSettings))); + mockFileSystem.AddFile(settingsPath, new(JsonSerializer.Serialize(testSettings, AppConstants.JsonSerializerOptions))); testSettings.AutoUpdateEnabled = true; await dataService.StoreAsync(testSettings, settingsPath); @@ -54,7 +55,7 @@ public async Task Store_FileAlreadyExists_FileReplaced() mockFileSystem.Directory.Exists(settingsDirectory).Should().BeTrue(); var storedFile = await mockFileSystem.File.ReadAllTextAsync(settingsPath); - var storedSettings = JsonConvert.DeserializeObject(storedFile); + var storedSettings = JsonSerializer.Deserialize(storedFile, AppConstants.JsonSerializerOptions); storedSettings.Should().BeEquivalentTo(testSettings); } } diff --git a/WoWsShipBuilder.Desktop/Features/Settings/SettingsComponent.razor b/WoWsShipBuilder.Desktop/Features/Settings/SettingsComponent.razor index d32cf0ca4..fef4f0885 100644 --- a/WoWsShipBuilder.Desktop/Features/Settings/SettingsComponent.razor +++ b/WoWsShipBuilder.Desktop/Features/Settings/SettingsComponent.razor @@ -12,7 +12,6 @@ @using Avalonia @using Avalonia.Controls @using Avalonia.Controls.ApplicationLifetimes -@using Newtonsoft.Json @using WoWsShipBuilder.Features.Builds @using WoWsShipBuilder.Infrastructure.ApplicationData @using WoWsShipBuilder.Infrastructure.Utility @@ -20,6 +19,7 @@ @using System.IO.Abstractions @using System.Net @using System.IO +@using System.Text.Json @using Avalonia.Platform.Storage @implements IDisposable @@ -335,7 +335,7 @@ customBuildImagePath = path; } } - + private async Task ExportSavedBuilds() { var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow!; @@ -344,10 +344,9 @@ if (path is not null) { var buildStrings = savedBuilds.Select(x => x.CreateShortStringFromBuild()).ToList(); - await using (var stream = FileSystem.File.CreateText($"{path}/builds.json")) + await using (var stream = FileSystem.File.Create($"{path}/builds.json")) { - var serializer = new JsonSerializer(); - serializer.Serialize(stream, buildStrings); + await JsonSerializer.SerializeAsync(stream, buildStrings, AppConstants.JsonSerializerOptions); } Snackbar.Add("Builds successfully exported.", Severity.Success); RefreshNotifierService.NotifyRefreshRequested(); @@ -363,9 +362,9 @@ IEnumerable? buildList; try { - buildList = JsonConvert.DeserializeObject>(buildsString); + buildList = JsonSerializer.Deserialize>(buildsString, AppConstants.JsonSerializerOptions); } - catch (JsonReaderException) + catch (JsonException) { Snackbar.Add("Corrupted JSON file.", Severity.Error); return; diff --git a/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/AwsClient.cs b/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/AwsClient.cs index 75778efa0..1e2ecc13d 100644 --- a/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/AwsClient.cs +++ b/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/AwsClient.cs @@ -3,6 +3,7 @@ using System.IO.Abstractions; using System.IO.Compression; using System.Net.Http; +using System.Net.Http.Json; using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -69,7 +70,7 @@ public async Task DownloadImages(IFileSystem fileSystem, string? fileName = null public async Task DownloadVersionInfo(ServerType serverType) { string url = @$"{Host}/api/{serverType.StringName()}/VersionInfo.json"; - return await GetJsonAsync(url) ?? throw new HttpRequestException("Unable to process VersionInfo response from AWS server."); + return await Client.GetFromJsonAsync(url, AppConstants.JsonSerializerOptions) ?? throw new HttpRequestException("Unable to process VersionInfo response from AWS server."); } public async Task DownloadFiles(ServerType serverType, List<(string, string)> relativeFilePaths, IProgress? downloadProgress = null) diff --git a/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/ClientBase.cs b/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/ClientBase.cs index 3356f3372..58e630807 100644 --- a/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/ClientBase.cs +++ b/WoWsShipBuilder.Desktop/Infrastructure/AwsClient/ClientBase.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using System.Net.Http; using System.Threading.Tasks; -using Newtonsoft.Json; using WoWsShipBuilder.Desktop.Infrastructure.Data; using WoWsShipBuilder.Infrastructure.ApplicationData; @@ -25,21 +23,7 @@ protected ClientBase(IDataService dataService, IAppDataService appDataService) protected virtual async Task DownloadFileAsync(Uri uri, string fileName) { - await using Stream stream = await Client.GetStreamAsync(uri); + await using var stream = await Client.GetStreamAsync(uri); await DataService.StoreAsync(stream, fileName); } - - protected virtual async Task GetJsonAsync(string url, JsonSerializer? customSerializer = null) - { - await using Stream stream = await Client.GetStreamAsync(url); - return GetJson(stream, customSerializer); - } - - internal T? GetJson(Stream stream, JsonSerializer? customSerializer = null) - { - using var streamReader = new StreamReader(stream); - using var jsonReader = new JsonTextReader(streamReader); - JsonSerializer serializer = customSerializer ?? new JsonSerializer(); - return serializer.Deserialize(jsonReader); - } } diff --git a/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopDataService.cs b/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopDataService.cs index 17b0b13c9..2bec6b512 100644 --- a/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopDataService.cs +++ b/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopDataService.cs @@ -2,8 +2,9 @@ using System.IO.Abstractions; using System.Runtime.Versioning; using System.Text; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; +using WoWsShipBuilder.Infrastructure.ApplicationData; namespace WoWsShipBuilder.Desktop.Infrastructure.Data; @@ -23,10 +24,10 @@ public async Task StoreStringAsync(string content, string path) await fileSystem.File.WriteAllTextAsync(path, content, Encoding.UTF8); } - public async Task StoreAsync(object content, string path) + public async Task StoreAsync(T content, string path) { CreateDirectory(path); - string fileContents = JsonConvert.SerializeObject(content); + string fileContents = JsonSerializer.Serialize(content, AppConstants.JsonSerializerOptions); await fileSystem.File.WriteAllTextAsync(path, fileContents, Encoding.UTF8); } @@ -37,10 +38,10 @@ public async Task StoreAsync(Stream stream, string path) await stream.CopyToAsync(fileStream); } - public void Store(object content, string path) + public void Store(T content, string path) { CreateDirectory(path); - string fileContents = JsonConvert.SerializeObject(content); + string fileContents = JsonSerializer.Serialize(content, AppConstants.JsonSerializerOptions); fileSystem.File.WriteAllText(path, fileContents, Encoding.UTF8); } @@ -59,13 +60,13 @@ public void Store(Stream stream, string path) public async Task LoadAsync(string path) { string contents = await fileSystem.File.ReadAllTextAsync(path, Encoding.UTF8); - return JsonConvert.DeserializeObject(contents); + return JsonSerializer.Deserialize(contents, AppConstants.JsonSerializerOptions); } public T? Load(string path) { string contents = fileSystem.File.ReadAllText(path, Encoding.UTF8); - return JsonConvert.DeserializeObject(contents); + return JsonSerializer.Deserialize(contents, AppConstants.JsonSerializerOptions); } public string CombinePaths(params string[] paths) diff --git a/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopUserDataService.cs b/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopUserDataService.cs index 7a5b12854..972a99523 100644 --- a/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopUserDataService.cs +++ b/WoWsShipBuilder.Desktop/Infrastructure/Data/DesktopUserDataService.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using DynamicData; -using Newtonsoft.Json; using WoWsShipBuilder.Features.Builds; using WoWsShipBuilder.Infrastructure.ApplicationData; @@ -42,44 +42,45 @@ public async Task> LoadBuildsAsync() } string path = dataService.CombinePaths(appDataService.DefaultAppDataDirectory, "builds.json"); - if (fileSystem.File.Exists(path)) + + if (!fileSystem.File.Exists(path)) { - List? buildList = null; - try - { - buildList = await dataService.LoadAsync>(path); - } - catch (JsonReaderException) - { - // silently fails - } + return Enumerable.Empty(); + } - if (buildList is not null) + List? buildList = null; + try + { + buildList = await dataService.LoadAsync>(path); + } + catch (JsonException) + { + // silently fails + } + + if (buildList is not null) + { + var builds = new List(); + foreach (string buildString in buildList) { - var builds = new List(); - foreach (string buildString in buildList) + try { - try - { - var build = Build.CreateBuildFromString(buildString); - if (AppData.ShipDictionary.ContainsKey(build.ShipIndex)) - { - builds.Add(build); - } - } - catch (FormatException) + var build = Build.CreateBuildFromString(buildString); + if (AppData.ShipDictionary.ContainsKey(build.ShipIndex)) { - // silently fails + builds.Add(build); } } - - savedBuilds = builds.DistinctBy(x => x.Hash).ToList(); + catch (FormatException) + { + // silently fails + } } - return savedBuilds ?? Enumerable.Empty(); + savedBuilds = builds.DistinctBy(x => x.Hash).ToList(); } - return Enumerable.Empty(); + return savedBuilds ?? Enumerable.Empty(); } public async Task ImportBuildsAsync(IEnumerable builds) diff --git a/WoWsShipBuilder.Desktop/Infrastructure/Data/IDataService.cs b/WoWsShipBuilder.Desktop/Infrastructure/Data/IDataService.cs index e1d150457..11c3617ad 100644 --- a/WoWsShipBuilder.Desktop/Infrastructure/Data/IDataService.cs +++ b/WoWsShipBuilder.Desktop/Infrastructure/Data/IDataService.cs @@ -7,11 +7,11 @@ public interface IDataService { Task StoreStringAsync(string content, string path); - Task StoreAsync(object content, string path); + Task StoreAsync(T content, string path); Task StoreAsync(Stream stream, string path); - void Store(object content, string path); + void Store(T content, string path); void Store(Stream stream, string path); diff --git a/WoWsShipBuilder.Desktop/Infrastructure/StaticConfiguration/ApplicationSettings.cs b/WoWsShipBuilder.Desktop/Infrastructure/StaticConfiguration/ApplicationSettings.cs index 22d98673a..ff7fccd8d 100644 --- a/WoWsShipBuilder.Desktop/Infrastructure/StaticConfiguration/ApplicationSettings.cs +++ b/WoWsShipBuilder.Desktop/Infrastructure/StaticConfiguration/ApplicationSettings.cs @@ -1,6 +1,6 @@ -using System.IO; using System.Reflection; -using Newtonsoft.Json; +using System.Text.Json; +using WoWsShipBuilder.Infrastructure.ApplicationData; namespace WoWsShipBuilder.Desktop.Infrastructure.StaticConfiguration; @@ -13,13 +13,11 @@ internal static class ApplicationSettings private static ApplicationOptions LoadOptions() { var resourceName = "WoWsShipBuilder.Desktop.Infrastructure.StaticConfiguration.ApplicationOptions.json"; - using Stream? stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); ApplicationOptions? result = null; if (stream != null) { - using StreamReader reader = new(stream); - string fileContent = reader.ReadToEnd(); - result = JsonConvert.DeserializeObject(fileContent); + result = JsonSerializer.Deserialize(stream, AppConstants.JsonSerializerOptions); } return result ?? new ApplicationOptions(); diff --git a/WoWsShipBuilder.Desktop/WoWsShipBuilder.Desktop.csproj b/WoWsShipBuilder.Desktop/WoWsShipBuilder.Desktop.csproj index 356d7daee..33eb76e02 100644 --- a/WoWsShipBuilder.Desktop/WoWsShipBuilder.Desktop.csproj +++ b/WoWsShipBuilder.Desktop/WoWsShipBuilder.Desktop.csproj @@ -40,7 +40,6 @@ - all diff --git a/WoWsShipBuilder.ReleaseTest/ReleaseTest.cs b/WoWsShipBuilder.ReleaseTest/ReleaseTest.cs index a907b0eed..b9f47522e 100644 --- a/WoWsShipBuilder.ReleaseTest/ReleaseTest.cs +++ b/WoWsShipBuilder.ReleaseTest/ReleaseTest.cs @@ -1,12 +1,13 @@ using System; using System.Net.Http; using System.Reflection; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json; using NUnit.Framework; using WoWsShipBuilder.Desktop.Features.BlazorWebView; +using WoWsShipBuilder.Infrastructure.ApplicationData; namespace WoWsShipBuilder.ReleaseTest; @@ -33,7 +34,7 @@ public async Task EnsureNewVersionIsIncrement() var client = new HttpClient(); client.DefaultRequestHeaders.Add("User-Agent", "C# test client"); var response = await client.GetStringAsync(uri); - var apiResponse = JsonConvert.DeserializeObject(response); + var apiResponse = JsonSerializer.Deserialize(response, AppConstants.JsonSerializerOptions); var lastVersionString = Regex.Replace(apiResponse!.Name, "[^0-9.]", string.Empty); var lastVersion = Version.Parse(lastVersionString); (lastVersion < version).Should().BeTrue(); diff --git a/WoWsShipBuilder.Web.Test/ServerAwsClientTest.cs b/WoWsShipBuilder.Web.Test/ServerAwsClientTest.cs index 3561c3128..f01961a7e 100644 --- a/WoWsShipBuilder.Web.Test/ServerAwsClientTest.cs +++ b/WoWsShipBuilder.Web.Test/ServerAwsClientTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -8,14 +9,11 @@ using Microsoft.Extensions.Options; using Moq; using Moq.Protected; -using Newtonsoft.Json; using NUnit.Framework; using WoWsShipBuilder.DataStructures.Ship; using WoWsShipBuilder.DataStructures.Versioning; -using WoWsShipBuilder.Infrastructure; using WoWsShipBuilder.Infrastructure.ApplicationData; using WoWsShipBuilder.Infrastructure.GameData; -using WoWsShipBuilder.Infrastructure.Utility; using WoWsShipBuilder.Web.Infrastructure; namespace WoWsShipBuilder.Web.Test; @@ -43,17 +41,17 @@ public async Task DownloadFiles() "SendAsync", ItExpr.Is(message => message.RequestUri!.AbsoluteUri.EndsWith("VersionInfo.json")), ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage { Content = new StringContent(JsonConvert.SerializeObject(testVersionInfo)) }); + .ReturnsAsync(new HttpResponseMessage { Content = new StringContent(JsonSerializer.Serialize(testVersionInfo, AppConstants.JsonSerializerOptions)) }); var shipRequestExpression = ItExpr.Is(message => message.RequestUri!.AbsolutePath.Equals("/api/live/Ship/Germany.json")); messageHandlerMock.Protected().Setup>( "SendAsync", shipRequestExpression, ItExpr.IsAny()) - .ReturnsAsync(new HttpResponseMessage { Content = new StringContent(JsonConvert.SerializeObject(shipDictionary)) }); + .ReturnsAsync(new HttpResponseMessage { Content = new StringContent(JsonSerializer.Serialize(shipDictionary, AppConstants.JsonSerializerOptions)) }); var cdnOptions = new CdnOptions { Host = "https://example.com"}; - IOptions? options = Options.Create(cdnOptions); + IOptions options = Options.Create(cdnOptions); var client = new ServerAwsClient(new(messageHandlerMock.Object), options, NullLogger.Instance); var versionInfo = await client.DownloadVersionInfo(ServerType.Live); diff --git a/WoWsShipBuilder.Web/Features/UserSettings/UserSettings.razor b/WoWsShipBuilder.Web/Features/UserSettings/UserSettings.razor index 4c342b679..dbd966b7c 100644 --- a/WoWsShipBuilder.Web/Features/UserSettings/UserSettings.razor +++ b/WoWsShipBuilder.Web/Features/UserSettings/UserSettings.razor @@ -8,7 +8,7 @@ @using WoWsShipBuilder.Infrastructure.Utility @using WoWsShipBuilder.Web.Infrastructure.BetaAccess @using WoWsShipBuilder.Features.Builds -@using Newtonsoft.Json +@using System.Text.Json @implements IDisposable @implements IAsyncDisposable @@ -304,17 +304,18 @@ { if (file is not null && file.ContentType.Equals("application/json")) { - using StreamReader reader = new(file.OpenReadStream()); - string buildsString = await reader.ReadToEndAsync(); IEnumerable? buildList; - try - { - buildList = JsonConvert.DeserializeObject>(buildsString); - } - catch (JsonReaderException) + await using (var fileStream = file.OpenReadStream()) { - Snackbar.Add("Corrupted JSON file.", Severity.Error); - return; + try + { + buildList = await JsonSerializer.DeserializeAsync>(fileStream, AppConstants.JsonSerializerOptions); + } + catch (JsonException) + { + Snackbar.Add("Corrupted JSON file.", Severity.Error); + return; + } } if (buildList is not null) diff --git a/WoWsShipBuilder.Web/Infrastructure/AppSettingsHelper.cs b/WoWsShipBuilder.Web/Infrastructure/AppSettingsHelper.cs index a50a7cc18..344b61b76 100644 --- a/WoWsShipBuilder.Web/Infrastructure/AppSettingsHelper.cs +++ b/WoWsShipBuilder.Web/Infrastructure/AppSettingsHelper.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using Microsoft.JSInterop; -using Newtonsoft.Json; using WoWsShipBuilder.Features.Settings; using WoWsShipBuilder.Infrastructure.ApplicationData; @@ -20,8 +20,7 @@ public WebSettingsAccessor(IJSRuntime runtime) public async Task LoadSettings() { await InitializeModule(); - var settingsString = await module.InvokeAsync("getAppSettings"); - var result = settingsString == null ? null : JsonConvert.DeserializeObject(settingsString); + var result = await module.InvokeAsync("getAppSettings"); if (result is not null) { result.StoreBuildOnShare = false; @@ -33,7 +32,7 @@ public WebSettingsAccessor(IJSRuntime runtime) public async Task SaveSettings(AppSettings appSettings) { await InitializeModule(); - await module.InvokeVoidAsync("setAppSettings", JsonConvert.SerializeObject(appSettings)); + await module.InvokeVoidAsync("setAppSettings", appSettings); } [MemberNotNull(nameof(module))] diff --git a/WoWsShipBuilder.Web/Infrastructure/Data/ServerAppDataService.cs b/WoWsShipBuilder.Web/Infrastructure/Data/ServerAppDataService.cs index d7c5e5867..847789d4c 100644 --- a/WoWsShipBuilder.Web/Infrastructure/Data/ServerAppDataService.cs +++ b/WoWsShipBuilder.Web/Infrastructure/Data/ServerAppDataService.cs @@ -1,6 +1,6 @@ using System.Globalization; +using System.Text.Json; using Microsoft.Extensions.Options; -using Newtonsoft.Json; using Sentry; using WoWsShipBuilder.DataStructures; using WoWsShipBuilder.DataStructures.Ship; @@ -73,7 +73,7 @@ public async Task LoadLocalFilesAsync(ServerType serverType) string dataRoot = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), shipBuilderDirectory, "json", serverType.StringName()); string versionInfoContent = await File.ReadAllTextAsync(Path.Join(dataRoot, "VersionInfo.json")); - var localVersionInfo = JsonConvert.DeserializeObject(versionInfoContent)!; + var localVersionInfo = JsonSerializer.Deserialize(versionInfoContent, AppConstants.JsonSerializerOptions)!; AppData.DataVersion = localVersionInfo.CurrentVersion.MainVersion.ToString(3) + "#" + localVersionInfo.CurrentVersion.DataIteration; var dataRootInfo = new DirectoryInfo(dataRoot); @@ -111,7 +111,7 @@ public async Task LoadLocalFilesAsync(ServerType serverType) } string fileContent = await File.ReadAllTextAsync(Path.Join(localizationRoot, $"{language}.json")); - return JsonConvert.DeserializeObject>(fileContent); + return JsonSerializer.Deserialize>(fileContent, AppConstants.JsonSerializerOptions); } if (awsClient is ServerAwsClient serverAwsClient) diff --git a/WoWsShipBuilder.Web/Infrastructure/Data/WebUserDataService.cs b/WoWsShipBuilder.Web/Infrastructure/Data/WebUserDataService.cs index 460ae686e..18c46e6fb 100644 --- a/WoWsShipBuilder.Web/Infrastructure/Data/WebUserDataService.cs +++ b/WoWsShipBuilder.Web/Infrastructure/Data/WebUserDataService.cs @@ -1,8 +1,8 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using DynamicData; using Microsoft.JSInterop; using MudBlazor; -using Newtonsoft.Json; using WoWsShipBuilder.Features.Builds; using WoWsShipBuilder.Infrastructure.ApplicationData; using JsonSerializer = System.Text.Json.JsonSerializer; @@ -33,7 +33,7 @@ public async Task SaveBuildsAsync(IEnumerable builds) { await InitializeModule(); var buildStrings = builds.Select(x => x.CreateShortStringFromBuild()); - await module.InvokeVoidAsync("saveData", BuildFileName, JsonSerializer.Serialize(buildStrings)); + await module.InvokeVoidAsync("saveData", BuildFileName, JsonSerializer.Serialize(buildStrings, AppConstants.JsonSerializerOptions)); } public async Task> LoadBuildsAsync() @@ -50,7 +50,7 @@ public async Task> LoadBuildsAsync() IEnumerable? buildList = null; try { - buildList = JsonSerializer.Deserialize>(buildStrings); + buildList = JsonSerializer.Deserialize>(buildStrings, AppConstants.JsonSerializerOptions); } catch (JsonException) { diff --git a/WoWsShipBuilder.Web/Infrastructure/ServerAwsClient.cs b/WoWsShipBuilder.Web/Infrastructure/ServerAwsClient.cs index ac8b247bf..4692788e1 100644 --- a/WoWsShipBuilder.Web/Infrastructure/ServerAwsClient.cs +++ b/WoWsShipBuilder.Web/Infrastructure/ServerAwsClient.cs @@ -1,11 +1,8 @@ using Microsoft.Extensions.Options; -using Newtonsoft.Json; using WoWsShipBuilder.DataStructures.Versioning; -using WoWsShipBuilder.Infrastructure; using WoWsShipBuilder.Infrastructure.ApplicationData; using WoWsShipBuilder.Infrastructure.GameData; using WoWsShipBuilder.Infrastructure.HttpClients; -using WoWsShipBuilder.Infrastructure.Utility; namespace WoWsShipBuilder.Web.Infrastructure; @@ -27,8 +24,7 @@ public ServerAwsClient(HttpClient httpClient, IOptions options, ILog public async Task DownloadVersionInfo(ServerType serverType) { var url = @$"{options.Host}/api/{serverType.StringName()}/VersionInfo.json"; - string stringContent = await httpClient.GetStringAsync(url); - return JsonConvert.DeserializeObject(stringContent) ?? throw new HttpRequestException("Unable to process VersionInfo response from AWS server."); + return await httpClient.GetFromJsonAsync(url, AppConstants.JsonSerializerOptions) ?? throw new HttpRequestException("Unable to process VersionInfo response from AWS server."); } public async Task DownloadFiles(ServerType serverType, List<(string, string)> relativeFilePaths, IProgress? downloadProgress = null) diff --git a/WoWsShipBuilder.Web/wwwroot/scripts/userDataService.js b/WoWsShipBuilder.Web/wwwroot/scripts/userDataService.js index de5175568..fffba4024 100644 --- a/WoWsShipBuilder.Web/wwwroot/scripts/userDataService.js +++ b/WoWsShipBuilder.Web/wwwroot/scripts/userDataService.js @@ -4,4 +4,4 @@ export function saveData(fileName, data) { window.localStorage[fileName] = data; -} \ No newline at end of file +}