From d3e8c7e0c94a3d2987329d278dc0d00ae3d76c8f Mon Sep 17 00:00:00 2001 From: ta264 Date: Wed, 10 Feb 2021 21:52:48 +0000 Subject: [PATCH] New: Use System.Text.Json for Nancy and SignalR --- src/NzbDrone.Common/Readarr.Common.csproj | 1 + .../{ => Newtonsoft.Json}/HttpUriConverter.cs | 0 .../{ => Newtonsoft.Json}/IntConverter.cs | 0 .../Serializer/{ => Newtonsoft.Json}/Json.cs | 5 -- .../{ => Newtonsoft.Json}/JsonVisitor.cs | 0 .../UnderscoreStringEnumConverter.cs | 0 .../PolymorphicWriteOnlyJsonConverter.cs | 19 ++++ .../System.Text.Json/STJHttpUriConverter.cs | 27 ++++++ .../System.Text.Json/STJTimeSpanConverter.cs} | 4 +- .../System.Text.Json/STJUtcConverter.cs | 19 ++++ .../System.Text.Json/STJVersionConverter.cs | 48 ++++++++++ .../Serializer/System.Text.Json/STJson.cs | 88 ++++++++++++++++++ .../Converters/EmbeddedDocumentConverter.cs | 5 +- .../Datastore/Converters/UtcConverter.cs | 15 ---- src/NzbDrone.Core/Datastore/LazyLoaded.cs | 7 +- .../Datastore/LazyLoadedConverterFactory.cs | 90 +++++++++++++++++++ .../Messaging/Commands/Command.cs | 3 + .../Qualities/QualityProfileQualityItem.cs | 4 +- src/NzbDrone.Core/Qualities/QualityModel.cs | 2 +- src/NzbDrone.Core/Readarr.Core.csproj | 2 +- .../ThingiProvider/ProviderRepository.cs | 8 +- src/NzbDrone.Host/Readarr.Host.csproj | 1 - .../WebHost/WebHostController.cs | 18 ++-- .../Client/ClientBase.cs | 5 +- .../IntegrationTestBase.cs | 2 + src/NzbDrone.SignalR/SignalRMessage.cs | 7 +- .../Readarr.Test.Common.csproj | 3 +- src/Readarr.Api.V1/Author/AuthorModule.cs | 5 +- src/Readarr.Api.V1/Author/AuthorResource.cs | 5 +- .../Commands/CommandResource.cs | 2 +- .../CustomFilters/CustomFilterResource.cs | 7 +- .../Indexers/ReleaseResource.cs | 10 +-- src/Readarr.Api.V1/Readarr.Api.V1.csproj | 1 - src/Readarr.Api.V1/Update/UpdateResource.cs | 2 - .../ClientSchema/SchemaBuilder.cs | 24 +++-- .../Extensions/NancyJsonSerializer.cs | 12 ++- .../Extensions/ReqResExtensions.cs | 4 +- src/Readarr.Http/REST/RestModule.cs | 6 +- src/Readarr.Http/REST/RestResource.cs | 4 +- src/Readarr.Http/Readarr.Http.csproj | 1 - 40 files changed, 378 insertions(+), 88 deletions(-) rename src/NzbDrone.Common/Serializer/{ => Newtonsoft.Json}/HttpUriConverter.cs (100%) rename src/NzbDrone.Common/Serializer/{ => Newtonsoft.Json}/IntConverter.cs (100%) rename src/NzbDrone.Common/Serializer/{ => Newtonsoft.Json}/Json.cs (94%) rename src/NzbDrone.Common/Serializer/{ => Newtonsoft.Json}/JsonVisitor.cs (100%) rename src/NzbDrone.Common/Serializer/{ => Newtonsoft.Json}/UnderscoreStringEnumConverter.cs (100%) create mode 100644 src/NzbDrone.Common/Serializer/System.Text.Json/PolymorphicWriteOnlyJsonConverter.cs create mode 100644 src/NzbDrone.Common/Serializer/System.Text.Json/STJHttpUriConverter.cs rename src/{NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs => NzbDrone.Common/Serializer/System.Text.Json/STJTimeSpanConverter.cs} (81%) create mode 100644 src/NzbDrone.Common/Serializer/System.Text.Json/STJUtcConverter.cs create mode 100644 src/NzbDrone.Common/Serializer/System.Text.Json/STJVersionConverter.cs create mode 100644 src/NzbDrone.Common/Serializer/System.Text.Json/STJson.cs create mode 100644 src/NzbDrone.Core/Datastore/LazyLoadedConverterFactory.cs diff --git a/src/NzbDrone.Common/Readarr.Common.csproj b/src/NzbDrone.Common/Readarr.Common.csproj index 21436d734..cabbb7a50 100644 --- a/src/NzbDrone.Common/Readarr.Common.csproj +++ b/src/NzbDrone.Common/Readarr.Common.csproj @@ -11,6 +11,7 @@ + diff --git a/src/NzbDrone.Common/Serializer/HttpUriConverter.cs b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/HttpUriConverter.cs similarity index 100% rename from src/NzbDrone.Common/Serializer/HttpUriConverter.cs rename to src/NzbDrone.Common/Serializer/Newtonsoft.Json/HttpUriConverter.cs diff --git a/src/NzbDrone.Common/Serializer/IntConverter.cs b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/IntConverter.cs similarity index 100% rename from src/NzbDrone.Common/Serializer/IntConverter.cs rename to src/NzbDrone.Common/Serializer/Newtonsoft.Json/IntConverter.cs diff --git a/src/NzbDrone.Common/Serializer/Json.cs b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/Json.cs similarity index 94% rename from src/NzbDrone.Common/Serializer/Json.cs rename to src/NzbDrone.Common/Serializer/Newtonsoft.Json/Json.cs index 47d5bce67..786c50920 100644 --- a/src/NzbDrone.Common/Serializer/Json.cs +++ b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/Json.cs @@ -77,10 +77,5 @@ public static void Serialize(TModel model, TextWriter outputStream) Serializer.Serialize(jsonTextWriter, model); jsonTextWriter.Flush(); } - - public static void Serialize(TModel model, Stream outputStream) - { - Serialize(model, new StreamWriter(outputStream)); - } } } diff --git a/src/NzbDrone.Common/Serializer/JsonVisitor.cs b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/JsonVisitor.cs similarity index 100% rename from src/NzbDrone.Common/Serializer/JsonVisitor.cs rename to src/NzbDrone.Common/Serializer/Newtonsoft.Json/JsonVisitor.cs diff --git a/src/NzbDrone.Common/Serializer/UnderscoreStringEnumConverter.cs b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/UnderscoreStringEnumConverter.cs similarity index 100% rename from src/NzbDrone.Common/Serializer/UnderscoreStringEnumConverter.cs rename to src/NzbDrone.Common/Serializer/Newtonsoft.Json/UnderscoreStringEnumConverter.cs diff --git a/src/NzbDrone.Common/Serializer/System.Text.Json/PolymorphicWriteOnlyJsonConverter.cs b/src/NzbDrone.Common/Serializer/System.Text.Json/PolymorphicWriteOnlyJsonConverter.cs new file mode 100644 index 000000000..7f4e76c50 --- /dev/null +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/PolymorphicWriteOnlyJsonConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NzbDrone.Common.Serializer +{ + public class PolymorphicWriteOnlyJsonConverter : JsonConverter + { + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + } +} diff --git a/src/NzbDrone.Common/Serializer/System.Text.Json/STJHttpUriConverter.cs b/src/NzbDrone.Common/Serializer/System.Text.Json/STJHttpUriConverter.cs new file mode 100644 index 000000000..cd9f1b46f --- /dev/null +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/STJHttpUriConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NzbDrone.Common.Http; + +namespace NzbDrone.Common.Serializer +{ + public class STJHttpUriConverter : JsonConverter + { + public override HttpUri Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new HttpUri(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, HttpUri value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.FullUri); + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs b/src/NzbDrone.Common/Serializer/System.Text.Json/STJTimeSpanConverter.cs similarity index 81% rename from src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs rename to src/NzbDrone.Common/Serializer/System.Text.Json/STJTimeSpanConverter.cs index 07f2d9314..58caacb2b 100644 --- a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/STJTimeSpanConverter.cs @@ -2,9 +2,9 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace NzbDrone.Core.Datastore.Converters +namespace NzbDrone.Common.Serializer { - public class TimeSpanConverter : JsonConverter + public class STJTimeSpanConverter : JsonConverter { public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/src/NzbDrone.Common/Serializer/System.Text.Json/STJUtcConverter.cs b/src/NzbDrone.Common/Serializer/System.Text.Json/STJUtcConverter.cs new file mode 100644 index 000000000..520b24d6c --- /dev/null +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/STJUtcConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NzbDrone.Common.Serializer +{ + public class STJUtcConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")); + } + } +} diff --git a/src/NzbDrone.Common/Serializer/System.Text.Json/STJVersionConverter.cs b/src/NzbDrone.Common/Serializer/System.Text.Json/STJVersionConverter.cs new file mode 100644 index 000000000..70ad492c3 --- /dev/null +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/STJVersionConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NzbDrone.Common.Serializer +{ + public class STJVersionConverter : JsonConverter + { + public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + else + { + if (reader.TokenType == JsonTokenType.String) + { + try + { + Version v = new Version(reader.GetString()); + return v; + } + catch (Exception) + { + throw new JsonException(); + } + } + else + { + throw new JsonException(); + } + } + } + + public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) + { + if (value == null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.ToString()); + } + } + } +} diff --git a/src/NzbDrone.Common/Serializer/System.Text.Json/STJson.cs b/src/NzbDrone.Common/Serializer/System.Text.Json/STJson.cs new file mode 100644 index 000000000..48b98cf6d --- /dev/null +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/STJson.cs @@ -0,0 +1,88 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NzbDrone.Common.Serializer +{ + public static class STJson + { + private static readonly JsonSerializerOptions SerializerSettings = GetSerializerSettings(); + private static readonly JsonWriterOptions WriterOptions = new JsonWriterOptions + { + Indented = true + }; + + public static JsonSerializerOptions GetSerializerSettings() + { + var serializerSettings = new JsonSerializerOptions + { + AllowTrailingCommas = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true)); + serializerSettings.Converters.Add(new STJVersionConverter()); + serializerSettings.Converters.Add(new STJHttpUriConverter()); + serializerSettings.Converters.Add(new STJTimeSpanConverter()); + serializerSettings.Converters.Add(new STJUtcConverter()); + + return serializerSettings; + } + + public static T Deserialize(string json) + where T : new() + { + return JsonSerializer.Deserialize(json, SerializerSettings); + } + + public static object Deserialize(string json, Type type) + { + return JsonSerializer.Deserialize(json, type, SerializerSettings); + } + + public static object Deserialize(Stream input, Type type) + { + return JsonSerializer.DeserializeAsync(input, type, SerializerSettings).GetAwaiter().GetResult(); + } + + public static bool TryDeserialize(string json, out T result) + where T : new() + { + try + { + result = Deserialize(json); + return true; + } + catch (JsonException) + { + result = default(T); + return false; + } + } + + public static string ToJson(object obj) + { + return JsonSerializer.Serialize(obj, SerializerSettings); + } + + public static void Serialize(TModel model, Stream outputStream, JsonSerializerOptions options = null) + { + if (options == null) + { + options = SerializerSettings; + } + + // Cast to object to get all properties written out + // https://github.com/dotnet/corefx/issues/38650 + using (var writer = new Utf8JsonWriter(outputStream, options: WriterOptions)) + { + JsonSerializer.Serialize(writer, (object)model, options); + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs index b9efdb194..d7e75d490 100644 --- a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs @@ -2,6 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Dapper; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Datastore.Converters { @@ -22,8 +23,8 @@ public EmbeddedDocumentConverter() }; serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true)); - serializerSettings.Converters.Add(new TimeSpanConverter()); - serializerSettings.Converters.Add(new UtcConverter()); + serializerSettings.Converters.Add(new STJTimeSpanConverter()); + serializerSettings.Converters.Add(new STJUtcConverter()); SerializerSettings = serializerSettings; } diff --git a/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs b/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs index de215a5f2..0a66eddc0 100644 --- a/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs @@ -1,7 +1,5 @@ using System; using System.Data; -using System.Text.Json; -using System.Text.Json.Serialization; using Dapper; namespace NzbDrone.Core.Datastore.Converters @@ -18,17 +16,4 @@ public override DateTime Parse(object value) return (DateTime)value; } } - - public class UtcConverter : JsonConverter - { - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return DateTime.Parse(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")); - } - } } diff --git a/src/NzbDrone.Core/Datastore/LazyLoaded.cs b/src/NzbDrone.Core/Datastore/LazyLoaded.cs index 91ff44c82..47e3d30c8 100644 --- a/src/NzbDrone.Core/Datastore/LazyLoaded.cs +++ b/src/NzbDrone.Core/Datastore/LazyLoaded.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Serialization; using NLog; using NzbDrone.Common.Instrumentation; @@ -15,6 +16,7 @@ public interface ILazyLoaded : ICloneable /// Allows a field to be lazy loaded. /// /// + [JsonConverter(typeof(LazyLoadedConverterFactory))] public class LazyLoaded : ILazyLoaded { protected TChild _value; @@ -62,11 +64,6 @@ public object Clone() { return MemberwiseClone(); } - - public bool ShouldSerializeValue() - { - return IsLoaded; - } } /// diff --git a/src/NzbDrone.Core/Datastore/LazyLoadedConverterFactory.cs b/src/NzbDrone.Core/Datastore/LazyLoadedConverterFactory.cs new file mode 100644 index 000000000..4e02ef794 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/LazyLoadedConverterFactory.cs @@ -0,0 +1,90 @@ +using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NzbDrone.Core.Datastore +{ + public class LazyLoadedConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + { + return false; + } + + return typeToConvert.GetGenericTypeDefinition() == typeof(LazyLoaded<>); + } + + public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) + { + var childType = type.GetGenericArguments()[0]; + + return (JsonConverter)Activator.CreateInstance( + typeof(LazyLoadedConverter<>).MakeGenericType(childType), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: new object[] { options }, + culture: null); + } + + private class LazyLoadedConverter : JsonConverter> + { + private readonly JsonConverter _childConverter; + private readonly Type _childType; + + public LazyLoadedConverter(JsonSerializerOptions options) + { + // For performance, use the existing converter if available. + _childConverter = (JsonConverter)options + .GetConverter(typeof(TChild)); + + // Cache the type. + _childType = typeof(TChild); + } + + public override LazyLoaded Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + TChild value; + if (_childConverter != null) + { + reader.Read(); + value = _childConverter.Read(ref reader, _childType, options); + } + else + { + value = JsonSerializer.Deserialize(ref reader, options); + } + + if (value != null) + { + return new LazyLoaded(value); + } + else + { + return null; + } + } + + public override void Write(Utf8JsonWriter writer, LazyLoaded value, JsonSerializerOptions options) + { + if (value.IsLoaded) + { + if (_childConverter != null) + { + _childConverter.Write(writer, value.Value, options); + } + else + { + JsonSerializer.Serialize(writer, value.Value, options); + } + } + else + { + writer.WriteNullValue(); + } + } + } + } +} diff --git a/src/NzbDrone.Core/Messaging/Commands/Command.cs b/src/NzbDrone.Core/Messaging/Commands/Command.cs index 4402bfe6c..666d66421 100644 --- a/src/NzbDrone.Core/Messaging/Commands/Command.cs +++ b/src/NzbDrone.Core/Messaging/Commands/Command.cs @@ -1,7 +1,10 @@ using System; +using System.Text.Json.Serialization; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Messaging.Commands { + [JsonConverter(typeof(PolymorphicWriteOnlyJsonConverter))] public abstract class Command { private bool _sendUpdatesToClient; diff --git a/src/NzbDrone.Core/Profiles/Qualities/QualityProfileQualityItem.cs b/src/NzbDrone.Core/Profiles/Qualities/QualityProfileQualityItem.cs index 4b5369749..4fe8dec67 100644 --- a/src/NzbDrone.Core/Profiles/Qualities/QualityProfileQualityItem.cs +++ b/src/NzbDrone.Core/Profiles/Qualities/QualityProfileQualityItem.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Qualities; @@ -9,7 +9,7 @@ namespace NzbDrone.Core.Profiles.Qualities { public class QualityProfileQualityItem : IEmbeddedDocument { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int Id { get; set; } public string Name { get; set; } diff --git a/src/NzbDrone.Core/Qualities/QualityModel.cs b/src/NzbDrone.Core/Qualities/QualityModel.cs index 77597e437..93bbe4857 100644 --- a/src/NzbDrone.Core/Qualities/QualityModel.cs +++ b/src/NzbDrone.Core/Qualities/QualityModel.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Qualities diff --git a/src/NzbDrone.Core/Readarr.Core.csproj b/src/NzbDrone.Core/Readarr.Core.csproj index 2c5ff2939..d80d4f727 100644 --- a/src/NzbDrone.Core/Readarr.Core.csproj +++ b/src/NzbDrone.Core/Readarr.Core.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs index 79defc9f1..626dafa19 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; using Dapper; using NzbDrone.Common.Extensions; using NzbDrone.Common.Reflection; +using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Datastore.Converters; using NzbDrone.Core.Messaging.Events; namespace NzbDrone.Core.ThingiProvider @@ -30,8 +30,8 @@ protected ProviderRepository(IMainDatabase database, IEventAggregator eventAggre }; serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true)); - serializerSettings.Converters.Add(new TimeSpanConverter()); - serializerSettings.Converters.Add(new UtcConverter()); + serializerSettings.Converters.Add(new STJTimeSpanConverter()); + serializerSettings.Converters.Add(new STJUtcConverter()); _serializerSettings = serializerSettings; } diff --git a/src/NzbDrone.Host/Readarr.Host.csproj b/src/NzbDrone.Host/Readarr.Host.csproj index 917c9d557..7373da28b 100644 --- a/src/NzbDrone.Host/Readarr.Host.csproj +++ b/src/NzbDrone.Host/Readarr.Host.csproj @@ -11,7 +11,6 @@ - diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs index 056b51db4..3c7245275 100644 --- a/src/NzbDrone.Host/WebHost/WebHostController.cs +++ b/src/NzbDrone.Host/WebHost/WebHostController.cs @@ -91,17 +91,15 @@ public void StartServer() services .AddSignalR() #if !NETCOREAPP - .AddJsonProtocol( - options => - { - options.PayloadSerializerSettings = Json.GetSerializerSettings(); - }); + .AddJsonProtocol(options => + { + options.PayloadSerializerSettings = Json.GetSerializerSettings(); + }); #else - .AddNewtonsoftJsonProtocol( - options => - { - options.PayloadSerializerSettings = Json.GetSerializerSettings(); - }); + .AddJsonProtocol(options => + { + options.PayloadSerializerOptions = STJson.GetSerializerSettings(); + }); #endif }) .Configure(app => diff --git a/src/NzbDrone.Integration.Test/Client/ClientBase.cs b/src/NzbDrone.Integration.Test/Client/ClientBase.cs index 31be63561..a73c96bdd 100644 --- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -51,7 +51,7 @@ public string Execute(IRestRequest request, HttpStatusCode statusCode) throw response.ErrorException; } - AssertDisableCache(response.Headers); + AssertDisableCache(response); response.ErrorMessage.Should().BeNullOrWhiteSpace(); @@ -68,9 +68,10 @@ public T Execute(IRestRequest request, HttpStatusCode statusCode) return Json.Deserialize(content); } - private static void AssertDisableCache(IList headers) + private static void AssertDisableCache(IRestResponse response) { // cache control header gets reordered on net core + var headers = response.Headers; ((string)headers.Single(c => c.Name == "Cache-Control").Value).Split(',').Select(x => x.Trim()) .Should().BeEquivalentTo("no-store, must-revalidate, no-cache, max-age=0".Split(',').Select(x => x.Trim())); headers.Single(c => c.Name == "Pragma").Value.Should().Be("no-cache"); diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index 261afb849..1059b9f31 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -28,6 +28,7 @@ using Readarr.Api.V1.RootFolders; using Readarr.Api.V1.Tags; using RestSharp; +using RestSharp.Serializers.SystemTextJson; namespace NzbDrone.Integration.Test { @@ -98,6 +99,7 @@ protected virtual void InitRestClients() RestClient = new RestClient(RootUrl + "api/v1/"); RestClient.AddDefaultHeader("Authentication", ApiKey); RestClient.AddDefaultHeader("X-Api-Key", ApiKey); + RestClient.UseSystemTextJson(); Blacklist = new ClientBase(RestClient, ApiKey); Commands = new CommandClient(RestClient, ApiKey); diff --git a/src/NzbDrone.SignalR/SignalRMessage.cs b/src/NzbDrone.SignalR/SignalRMessage.cs index 548d988a9..81a0a2b2c 100644 --- a/src/NzbDrone.SignalR/SignalRMessage.cs +++ b/src/NzbDrone.SignalR/SignalRMessage.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json; using NzbDrone.Core.Datastore.Events; namespace NzbDrone.SignalR @@ -8,7 +7,11 @@ public class SignalRMessage public object Body { get; set; } public string Name { get; set; } - [JsonIgnore] +#if !NETCOREAPP + [Newtonsoft.Json.JsonIgnore] +#else + [System.Text.Json.Serialization.JsonIgnore] +#endif public ModelAction Action { get; set; } } } diff --git a/src/NzbDrone.Test.Common/Readarr.Test.Common.csproj b/src/NzbDrone.Test.Common/Readarr.Test.Common.csproj index 6be3ecdf3..08d05d467 100644 --- a/src/NzbDrone.Test.Common/Readarr.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/Readarr.Test.Common.csproj @@ -9,7 +9,8 @@ - + + diff --git a/src/Readarr.Api.V1/Author/AuthorModule.cs b/src/Readarr.Api.V1/Author/AuthorModule.cs index 4d13ccc45..cbc67d3b4 100644 --- a/src/Readarr.Api.V1/Author/AuthorModule.cs +++ b/src/Readarr.Api.V1/Author/AuthorModule.cs @@ -16,6 +16,7 @@ using NzbDrone.Core.Validation; using NzbDrone.Core.Validation.Paths; using NzbDrone.SignalR; +using Readarr.Api.V1.Books; using Readarr.Http; using Readarr.Http.Extensions; @@ -189,8 +190,8 @@ private void LinkNextPreviousBooks(params AuthorResource[] authors) foreach (var authorResource in authors) { - authorResource.NextBook = nextBooks.FirstOrDefault(x => x.AuthorMetadataId == authorResource.AuthorMetadataId); - authorResource.LastBook = lastBooks.FirstOrDefault(x => x.AuthorMetadataId == authorResource.AuthorMetadataId); + authorResource.NextBook = nextBooks.FirstOrDefault(x => x.AuthorMetadataId == authorResource.AuthorMetadataId).ToResource(); + authorResource.LastBook = lastBooks.FirstOrDefault(x => x.AuthorMetadataId == authorResource.AuthorMetadataId).ToResource(); } } diff --git a/src/Readarr.Api.V1/Author/AuthorResource.cs b/src/Readarr.Api.V1/Author/AuthorResource.cs index e55867e42..9020041ab 100644 --- a/src/Readarr.Api.V1/Author/AuthorResource.cs +++ b/src/Readarr.Api.V1/Author/AuthorResource.cs @@ -5,6 +5,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Books; using NzbDrone.Core.MediaCover; +using Readarr.Api.V1.Books; using Readarr.Http.REST; namespace Readarr.Api.V1.Author @@ -27,8 +28,8 @@ public class AuthorResource : RestResource public string Disambiguation { get; set; } public List Links { get; set; } - public Book NextBook { get; set; } - public Book LastBook { get; set; } + public BookResource NextBook { get; set; } + public BookResource LastBook { get; set; } public List Images { get; set; } diff --git a/src/Readarr.Api.V1/Commands/CommandResource.cs b/src/Readarr.Api.V1/Commands/CommandResource.cs index 91412bb85..30f5dbd08 100644 --- a/src/Readarr.Api.V1/Commands/CommandResource.cs +++ b/src/Readarr.Api.V1/Commands/CommandResource.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using NzbDrone.Common.Extensions; using NzbDrone.Core.Messaging.Commands; using Readarr.Http.REST; diff --git a/src/Readarr.Api.V1/CustomFilters/CustomFilterResource.cs b/src/Readarr.Api.V1/CustomFilters/CustomFilterResource.cs index 6372bdd0f..02cc53320 100644 --- a/src/Readarr.Api.V1/CustomFilters/CustomFilterResource.cs +++ b/src/Readarr.Api.V1/CustomFilters/CustomFilterResource.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Dynamic; using System.Linq; using NzbDrone.Common.Serializer; using NzbDrone.Core.CustomFilters; @@ -10,7 +11,7 @@ public class CustomFilterResource : RestResource { public string Type { get; set; } public string Label { get; set; } - public List Filters { get; set; } + public List Filters { get; set; } } public static class CustomFilterResourceMapper @@ -27,7 +28,7 @@ public static CustomFilterResource ToResource(this CustomFilter model) Id = model.Id, Type = model.Type, Label = model.Label, - Filters = Json.Deserialize>(model.Filters) + Filters = STJson.Deserialize>(model.Filters) }; } @@ -43,7 +44,7 @@ public static CustomFilter ToModel(this CustomFilterResource resource) Id = resource.Id, Type = resource.Type, Label = resource.Label, - Filters = Json.ToJson(resource.Filters) + Filters = STJson.ToJson(resource.Filters) }; } diff --git a/src/Readarr.Api.V1/Indexers/ReleaseResource.cs b/src/Readarr.Api.V1/Indexers/ReleaseResource.cs index e26b22f18..58b588e90 100644 --- a/src/Readarr.Api.V1/Indexers/ReleaseResource.cs +++ b/src/Readarr.Api.V1/Indexers/ReleaseResource.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; +using System.Text.Json.Serialization; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; @@ -49,14 +49,10 @@ public class ReleaseResource : RestResource public DownloadProtocol Protocol { get; set; } // Sent when queuing an unknown release - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - - // [JsonIgnore] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? AuthorId { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - - // [JsonIgnore] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int? BookId { get; set; } } diff --git a/src/Readarr.Api.V1/Readarr.Api.V1.csproj b/src/Readarr.Api.V1/Readarr.Api.V1.csproj index c7571e78c..5d72f9bce 100644 --- a/src/Readarr.Api.V1/Readarr.Api.V1.csproj +++ b/src/Readarr.Api.V1/Readarr.Api.V1.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Readarr.Api.V1/Update/UpdateResource.cs b/src/Readarr.Api.V1/Update/UpdateResource.cs index 79c319070..c4d0a7f66 100644 --- a/src/Readarr.Api.V1/Update/UpdateResource.cs +++ b/src/Readarr.Api.V1/Update/UpdateResource.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; using NzbDrone.Core.Update; using Readarr.Http.REST; @@ -9,7 +8,6 @@ namespace Readarr.Api.V1.Update { public class UpdateResource : RestResource { - [JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))] public Version Version { get; set; } public string Branch { get; set; } diff --git a/src/Readarr.Http/ClientSchema/SchemaBuilder.cs b/src/Readarr.Http/ClientSchema/SchemaBuilder.cs index 9d2ee0518..c9f5953cc 100644 --- a/src/Readarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Readarr.Http/ClientSchema/SchemaBuilder.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Newtonsoft.Json.Linq; +using System.Text.Json; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Extensions; using NzbDrone.Common.Reflection; +using NzbDrone.Common.Serializer; using NzbDrone.Core.Annotations; namespace Readarr.Http.ClientSchema @@ -213,9 +214,9 @@ private static Func GetValueConverter(Type propertyType) { return Enumerable.Empty(); } - else if (fieldValue.GetType() == typeof(JArray)) + else if (fieldValue is JsonElement e && e.ValueKind == JsonValueKind.Array) { - return ((JArray)fieldValue).Select(s => s.Value()); + return e.EnumerateArray().Select(s => s.GetInt32()); } else { @@ -231,9 +232,9 @@ private static Func GetValueConverter(Type propertyType) { return Enumerable.Empty(); } - else if (fieldValue.GetType() == typeof(JArray)) + else if (fieldValue is JsonElement e && e.ValueKind == JsonValueKind.Array) { - return ((JArray)fieldValue).Select(s => s.Value()); + return e.EnumerateArray().Select(s => s.GetString()); } else { @@ -243,7 +244,18 @@ private static Func GetValueConverter(Type propertyType) } else { - return fieldValue => fieldValue; + return fieldValue => + { + var element = fieldValue as JsonElement?; + + if (element == null || !element.HasValue) + { + return null; + } + + var json = element.Value.GetRawText(); + return STJson.Deserialize(json, propertyType); + }; } } diff --git a/src/Readarr.Http/Extensions/NancyJsonSerializer.cs b/src/Readarr.Http/Extensions/NancyJsonSerializer.cs index c62a6c75b..af4eb67ed 100644 --- a/src/Readarr.Http/Extensions/NancyJsonSerializer.cs +++ b/src/Readarr.Http/Extensions/NancyJsonSerializer.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; +using System.Text.Json; using Nancy; using Nancy.Responses.Negotiation; using NzbDrone.Common.Serializer; @@ -8,6 +9,13 @@ namespace Readarr.Http.Extensions { public class NancyJsonSerializer : ISerializer { + protected readonly JsonSerializerOptions _serializerSettings; + + public NancyJsonSerializer() + { + _serializerSettings = STJson.GetSerializerSettings(); + } + public bool CanSerialize(MediaRange contentType) { return contentType == "application/json"; @@ -15,7 +23,7 @@ public bool CanSerialize(MediaRange contentType) public void Serialize(MediaRange contentType, TModel model, Stream outputStream) { - Json.Serialize(model, outputStream); + STJson.Serialize(model, outputStream, _serializerSettings); } public IEnumerable Extensions { get; private set; } diff --git a/src/Readarr.Http/Extensions/ReqResExtensions.cs b/src/Readarr.Http/Extensions/ReqResExtensions.cs index e2456c369..f0c356ea2 100644 --- a/src/Readarr.Http/Extensions/ReqResExtensions.cs +++ b/src/Readarr.Http/Extensions/ReqResExtensions.cs @@ -27,10 +27,8 @@ public static T FromJson(this Stream body, Type type) public static object FromJson(this Stream body, Type type) { - var reader = new StreamReader(body, true); body.Position = 0; - var value = reader.ReadToEnd(); - return Json.Deserialize(value, type); + return STJson.Deserialize(body, type); } public static JsonResponse AsResponse(this TModel model, NancyContext context, HttpStatusCode statusCode = HttpStatusCode.OK) diff --git a/src/Readarr.Http/REST/RestModule.cs b/src/Readarr.Http/REST/RestModule.cs index 4b2c4d73a..d7d154923 100644 --- a/src/Readarr.Http/REST/RestModule.cs +++ b/src/Readarr.Http/REST/RestModule.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using FluentValidation; using FluentValidation.Results; using Nancy; using Nancy.Responses.Negotiation; -using Newtonsoft.Json; using NzbDrone.Core.Datastore; using Readarr.Http.Extensions; @@ -248,9 +248,9 @@ protected TResource ReadResourceFromRequest(bool skipValidate = false, bool skip { resource = Request.Body.FromJson(); } - catch (JsonReaderException ex) + catch (JsonException e) { - throw new BadRequestException(ex.Message); + throw new BadRequestException(e.Message); } if (resource == null) diff --git a/src/Readarr.Http/REST/RestResource.cs b/src/Readarr.Http/REST/RestResource.cs index 84307501d..c00c2aa9b 100644 --- a/src/Readarr.Http/REST/RestResource.cs +++ b/src/Readarr.Http/REST/RestResource.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Readarr.Http.REST { public abstract class RestResource { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int Id { get; set; } [JsonIgnore] diff --git a/src/Readarr.Http/Readarr.Http.csproj b/src/Readarr.Http/Readarr.Http.csproj index d93ffc7dd..3aa601fe0 100644 --- a/src/Readarr.Http/Readarr.Http.csproj +++ b/src/Readarr.Http/Readarr.Http.csproj @@ -7,7 +7,6 @@ -