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 96% rename from src/NzbDrone.Common/Serializer/Json.cs rename to src/NzbDrone.Common/Serializer/Newtonsoft.Json/Json.cs index 8a23c3f86..fce2f295f 100644 --- a/src/NzbDrone.Common/Serializer/Json.cs +++ b/src/NzbDrone.Common/Serializer/Newtonsoft.Json/Json.cs @@ -127,10 +127,5 @@ namespace NzbDrone.Common.Serializer 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..2e99401ab --- /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) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + 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 78% rename from src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs rename to src/NzbDrone.Common/Serializer/System.Text.Json/STJTimeSpanConverter.cs index 07f2d9314..870993551 100644 --- a/src/NzbDrone.Core/Datastore/Converters/TimeSpanConverter.cs +++ b/src/NzbDrone.Common/Serializer/System.Text.Json/STJTimeSpanConverter.cs @@ -1,10 +1,10 @@ -using System; +using System; 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 d4073e1d1..909c7e7e3 100644 --- a/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/EmbeddedDocumentConverter.cs @@ -2,6 +2,7 @@ using System.Data; using System.Text.Json; using System.Text.Json.Serialization; using Dapper; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Datastore.Converters { @@ -22,8 +23,8 @@ namespace NzbDrone.Core.Datastore.Converters }; 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.Converters.Add(new DictionaryStringObjectJsonConverter()); SerializerSettings = serializerSettings; diff --git a/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs b/src/NzbDrone.Core/Datastore/Converters/UtcConverter.cs index db70f2359..cbd379ed2 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 @@ namespace NzbDrone.Core.Datastore.Converters return DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc); } } - - 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..288726efb 100644 --- a/src/NzbDrone.Core/Datastore/LazyLoaded.cs +++ b/src/NzbDrone.Core/Datastore/LazyLoaded.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Text.Json.Serialization; using NLog; using NzbDrone.Common.Instrumentation; @@ -15,6 +16,7 @@ namespace NzbDrone.Core.Datastore /// Allows a field to be lazy loaded. /// /// + [JsonConverter(typeof(LazyLoadedConverterFactory))] public class LazyLoaded : ILazyLoaded { protected TChild _value; @@ -62,11 +64,6 @@ namespace NzbDrone.Core.Datastore { 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 0a41c9078..aa3ffa685 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/ThingiProvider/ProviderRepository.cs b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs index 36f058c7a..c721ea790 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs @@ -1,9 +1,10 @@ -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; @@ -29,8 +30,8 @@ namespace NzbDrone.Core.ThingiProvider }; 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.Converters.Add(new DictionaryStringObjectJsonConverter()); _serializerSettings = serializerSettings; diff --git a/src/NzbDrone.Host/Prowlarr.Host.csproj b/src/NzbDrone.Host/Prowlarr.Host.csproj index 323c8827a..ce748907c 100644 --- a/src/NzbDrone.Host/Prowlarr.Host.csproj +++ b/src/NzbDrone.Host/Prowlarr.Host.csproj @@ -5,7 +5,6 @@ - diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs index a3847290a..d0bc17b98 100644 --- a/src/NzbDrone.Host/WebHost/WebHostController.cs +++ b/src/NzbDrone.Host/WebHost/WebHostController.cs @@ -107,9 +107,9 @@ namespace Prowlarr.Host { services .AddSignalR() - .AddNewtonsoftJsonProtocol(options => + .AddJsonProtocol(options => { - options.PayloadSerializerSettings = Json.GetSerializerSettings(); + options.PayloadSerializerOptions = STJson.GetSerializerSettings(); }); }) .Configure(app => diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index 580312645..43a245ef9 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -18,6 +18,7 @@ using Prowlarr.Api.V1.Config; using Prowlarr.Api.V1.History; using Prowlarr.Api.V1.Tags; using RestSharp; +using RestSharp.Serializers.SystemTextJson; namespace NzbDrone.Integration.Test { @@ -79,6 +80,7 @@ namespace NzbDrone.Integration.Test RestClient = new RestClient(RootUrl + "api/v1/"); RestClient.AddDefaultHeader("Authentication", ApiKey); RestClient.AddDefaultHeader("X-Api-Key", ApiKey); + RestClient.UseSystemTextJson(); Commands = new CommandClient(RestClient, ApiKey); History = new ClientBase(RestClient, ApiKey); diff --git a/src/NzbDrone.SignalR/SignalRMessage.cs b/src/NzbDrone.SignalR/SignalRMessage.cs index 548d988a9..4140359b2 100644 --- a/src/NzbDrone.SignalR/SignalRMessage.cs +++ b/src/NzbDrone.SignalR/SignalRMessage.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; using NzbDrone.Core.Datastore.Events; namespace NzbDrone.SignalR diff --git a/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj b/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj index 91df754e5..03eaaea79 100644 --- a/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/Prowlarr.Test.Common.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs b/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs index 73b546d1f..69b142ef0 100644 --- a/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs +++ b/src/Prowlarr.Api.V1/Applications/ApplicationResource.cs @@ -2,7 +2,7 @@ using NzbDrone.Core.Applications; namespace Prowlarr.Api.V1.Application { - public class ApplicationResource : ProviderResource + public class ApplicationResource : ProviderResource { public ApplicationSyncLevel SyncLevel { get; set; } public string TestCommand { get; set; } diff --git a/src/Prowlarr.Api.V1/Commands/CommandResource.cs b/src/Prowlarr.Api.V1/Commands/CommandResource.cs index 0bb8d109f..589f50eac 100644 --- a/src/Prowlarr.Api.V1/Commands/CommandResource.cs +++ b/src/Prowlarr.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 Prowlarr.Http.REST; diff --git a/src/Prowlarr.Api.V1/CustomFilters/CustomFilterResource.cs b/src/Prowlarr.Api.V1/CustomFilters/CustomFilterResource.cs index f01ea2e79..9b6767dc6 100644 --- a/src/Prowlarr.Api.V1/CustomFilters/CustomFilterResource.cs +++ b/src/Prowlarr.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 @@ namespace Prowlarr.Api.V1.CustomFilters { 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 @@ namespace Prowlarr.Api.V1.CustomFilters Id = model.Id, Type = model.Type, Label = model.Label, - Filters = Json.Deserialize>(model.Filters) + Filters = STJson.Deserialize>(model.Filters) }; } @@ -43,7 +44,7 @@ namespace Prowlarr.Api.V1.CustomFilters Id = resource.Id, Type = resource.Type, Label = resource.Label, - Filters = Json.ToJson(resource.Filters) + Filters = STJson.ToJson(resource.Filters) }; } diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerCapabilityResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerCapabilityResource.cs index 4e137a8f2..d73545f0a 100644 --- a/src/Prowlarr.Api.V1/Indexers/IndexerCapabilityResource.cs +++ b/src/Prowlarr.Api.V1/Indexers/IndexerCapabilityResource.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using NzbDrone.Core.Indexers; using Prowlarr.Http.REST; @@ -12,8 +10,7 @@ namespace Prowlarr.Api.V1.Indexers { public int? LimitsMax { get; set; } public int? LimitsDefault { get; set; } - - public List Categories; + public List Categories { get; set; } } public static class IndexerCapabilitiesResourceMapper diff --git a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs index 35e94a186..dbbf3d2da 100644 --- a/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs +++ b/src/Prowlarr.Api.V1/Indexers/IndexerResource.cs @@ -8,7 +8,7 @@ using Prowlarr.Http.ClientSchema; namespace Prowlarr.Api.V1.Indexers { - public class IndexerResource : ProviderResource + public class IndexerResource : ProviderResource { public string BaseUrl { get; set; } public bool Enable { get; set; } diff --git a/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs b/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs index 0074467f1..290610e3a 100644 --- a/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs +++ b/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs @@ -2,7 +2,7 @@ using NzbDrone.Core.Notifications; namespace Prowlarr.Api.V1.Notifications { - public class NotificationResource : ProviderResource + public class NotificationResource : ProviderResource { public string Link { get; set; } public bool OnHealthIssue { get; set; } diff --git a/src/Prowlarr.Api.V1/ProviderModuleBase.cs b/src/Prowlarr.Api.V1/ProviderModuleBase.cs index d93045ef7..f3bb52c03 100644 --- a/src/Prowlarr.Api.V1/ProviderModuleBase.cs +++ b/src/Prowlarr.Api.V1/ProviderModuleBase.cs @@ -14,7 +14,7 @@ namespace Prowlarr.Api.V1 public abstract class ProviderModuleBase : ProwlarrRestModule where TProviderDefinition : ProviderDefinition, new() where TProvider : IProvider - where TProviderResource : ProviderResource, new() + where TProviderResource : ProviderResource, new() { protected readonly IProviderFactory _providerFactory; protected readonly ProviderResourceMapper _resourceMapper; @@ -124,12 +124,9 @@ namespace Prowlarr.Api.V1 var providerResource = _resourceMapper.ToResource(providerDefinition); var presetDefinitions = _providerFactory.GetPresetDefinitions(providerDefinition); - providerResource.Presets = presetDefinitions.Select(v => - { - var presetResource = _resourceMapper.ToResource(v); - - return presetResource as ProviderResource; - }).ToList(); + providerResource.Presets = presetDefinitions + .Select(v => _resourceMapper.ToResource(v)) + .ToList(); result.Add(providerResource); } diff --git a/src/Prowlarr.Api.V1/ProviderResource.cs b/src/Prowlarr.Api.V1/ProviderResource.cs index 240ca3591..abc09e2c9 100644 --- a/src/Prowlarr.Api.V1/ProviderResource.cs +++ b/src/Prowlarr.Api.V1/ProviderResource.cs @@ -6,7 +6,7 @@ using Prowlarr.Http.REST; namespace Prowlarr.Api.V1 { - public class ProviderResource : RestResource + public class ProviderResource : RestResource { public string Name { get; set; } public List Fields { get; set; } @@ -17,11 +17,11 @@ namespace Prowlarr.Api.V1 public ProviderMessage Message { get; set; } public HashSet Tags { get; set; } - public List Presets { get; set; } + public List Presets { get; set; } } public class ProviderResourceMapper - where TProviderResource : ProviderResource, new() + where TProviderResource : ProviderResource, new() where TProviderDefinition : ProviderDefinition, new() { public virtual TProviderResource ToResource(TProviderDefinition definition) diff --git a/src/Prowlarr.Api.V1/Prowlarr.Api.V1.csproj b/src/Prowlarr.Api.V1/Prowlarr.Api.V1.csproj index 40cda7ea4..0edca5cd2 100644 --- a/src/Prowlarr.Api.V1/Prowlarr.Api.V1.csproj +++ b/src/Prowlarr.Api.V1/Prowlarr.Api.V1.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Prowlarr.Api.V1/Update/UpdateResource.cs b/src/Prowlarr.Api.V1/Update/UpdateResource.cs index c58027165..e347b21e7 100644 --- a/src/Prowlarr.Api.V1/Update/UpdateResource.cs +++ b/src/Prowlarr.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 Prowlarr.Http.REST; @@ -9,7 +8,6 @@ namespace Prowlarr.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/Prowlarr.Http/ClientSchema/SchemaBuilder.cs b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs index 0b37bbdbd..0874ec7d6 100644 --- a/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Prowlarr.Http/ClientSchema/SchemaBuilder.cs @@ -2,10 +2,11 @@ using System; 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 Prowlarr.Http.ClientSchema @@ -221,9 +222,9 @@ namespace Prowlarr.Http.ClientSchema { return fieldValue => { - if (fieldValue.GetType() == typeof(JArray)) + if (fieldValue is JsonElement e && e.ValueKind == JsonValueKind.Array) { - return ((JArray)fieldValue).Select(s => s.Value()); + return e.EnumerateArray().Select(s => s.GetInt32()); } else { @@ -235,9 +236,9 @@ namespace Prowlarr.Http.ClientSchema { return fieldValue => { - if (fieldValue.GetType() == typeof(JArray)) + if (fieldValue is JsonElement e && e.ValueKind == JsonValueKind.Array) { - return ((JArray)fieldValue).Select(s => s.Value()); + return e.EnumerateArray().Select(s => s.GetString()); } else { @@ -247,7 +248,18 @@ namespace Prowlarr.Http.ClientSchema } 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/Prowlarr.Http/ErrorManagement/ProwlarrErrorPipeline.cs b/src/Prowlarr.Http/ErrorManagement/ProwlarrErrorPipeline.cs index c524f1140..fefd7dd7a 100644 --- a/src/Prowlarr.Http/ErrorManagement/ProwlarrErrorPipeline.cs +++ b/src/Prowlarr.Http/ErrorManagement/ProwlarrErrorPipeline.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Data.SQLite; using FluentValidation; using Nancy; +using Nancy.Extensions; +using Nancy.IO; using NLog; using NzbDrone.Core.Datastore; using NzbDrone.Core.Exceptions; @@ -26,7 +28,10 @@ namespace Prowlarr.Http.ErrorManagement if (exception is ApiException apiException) { - _logger.Warn(apiException, "API Error"); + _logger.Warn(apiException, "API Error:\n{0}", apiException.Message); + var body = RequestStream.FromStream(context.Request.Body).AsString(); + _logger.Trace("Request body:\n{0}", body); + return apiException.ToErrorResponse(context); } diff --git a/src/Prowlarr.Http/Extensions/NancyJsonSerializer.cs b/src/Prowlarr.Http/Extensions/NancyJsonSerializer.cs index 7be6b10d0..5a548545a 100644 --- a/src/Prowlarr.Http/Extensions/NancyJsonSerializer.cs +++ b/src/Prowlarr.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 Prowlarr.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 @@ namespace Prowlarr.Http.Extensions 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/Prowlarr.Http/Extensions/ReqResExtensions.cs b/src/Prowlarr.Http/Extensions/ReqResExtensions.cs index 8d805a7f2..864dc2a7e 100644 --- a/src/Prowlarr.Http/Extensions/ReqResExtensions.cs +++ b/src/Prowlarr.Http/Extensions/ReqResExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using Nancy; @@ -27,10 +27,8 @@ namespace Prowlarr.Http.Extensions 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/Prowlarr.Http/Prowlarr.Http.csproj b/src/Prowlarr.Http/Prowlarr.Http.csproj index 13d3443e9..f47044db6 100644 --- a/src/Prowlarr.Http/Prowlarr.Http.csproj +++ b/src/Prowlarr.Http/Prowlarr.Http.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Prowlarr.Http/REST/RestModule.cs b/src/Prowlarr.Http/REST/RestModule.cs index 82bf9c30d..678ad76c4 100644 --- a/src/Prowlarr.Http/REST/RestModule.cs +++ b/src/Prowlarr.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 Prowlarr.Http.Extensions; @@ -233,7 +233,7 @@ namespace Prowlarr.Http.REST { resource = Request.Body.FromJson(); } - catch (JsonReaderException e) + catch (JsonException e) { throw new BadRequestException($"Invalid request body. {e.Message}"); } diff --git a/src/Prowlarr.Http/REST/RestResource.cs b/src/Prowlarr.Http/REST/RestResource.cs index fae4a3195..b48436e27 100644 --- a/src/Prowlarr.Http/REST/RestResource.cs +++ b/src/Prowlarr.Http/REST/RestResource.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Prowlarr.Http.REST { public abstract class RestResource { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public virtual int Id { get; set; } [JsonIgnore]