diff --git a/src/Lidarr.Api.V1/Commands/CommandResource.cs b/src/Lidarr.Api.V1/Commands/CommandResource.cs index e6521cd2b..25b1032ba 100644 --- a/src/Lidarr.Api.V1/Commands/CommandResource.cs +++ b/src/Lidarr.Api.V1/Commands/CommandResource.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using Lidarr.Http.REST; -using Newtonsoft.Json; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Messaging.Commands; diff --git a/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs b/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs index 6f5dfaf0e..32f9eb090 100644 --- a/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs +++ b/src/Lidarr.Api.V1/CustomFilters/CustomFilterResource.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Dynamic; using System.Linq; using Lidarr.Http.REST; using NzbDrone.Common.Serializer; @@ -10,7 +11,7 @@ namespace Lidarr.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 Lidarr.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 Lidarr.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/Lidarr.Api.V1/Indexers/ReleaseResource.cs b/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs index b1cf31804..de363cec7 100644 --- a/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs +++ b/src/Lidarr.Api.V1/Indexers/ReleaseResource.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using Lidarr.Http.REST; -using Newtonsoft.Json; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; @@ -49,12 +49,12 @@ namespace Lidarr.Api.V1.Indexers public DownloadProtocol Protocol { get; set; } // Sent when queuing an unknown release - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] // [JsonIgnore] public int? ArtistId { get; set; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] // [JsonIgnore] public int? AlbumId { get; set; } diff --git a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj index 5e76f195d..7884bbe20 100644 --- a/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj +++ b/src/Lidarr.Api.V1/Lidarr.Api.V1.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Lidarr.Api.V1/Update/UpdateResource.cs b/src/Lidarr.Api.V1/Update/UpdateResource.cs index 489ad1a74..a0a419019 100644 --- a/src/Lidarr.Api.V1/Update/UpdateResource.cs +++ b/src/Lidarr.Api.V1/Update/UpdateResource.cs @@ -1,15 +1,13 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Lidarr.Http.REST; -using Newtonsoft.Json; using NzbDrone.Core.Update; namespace Lidarr.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/Lidarr.Http/ClientSchema/SchemaBuilder.cs b/src/Lidarr.Http/ClientSchema/SchemaBuilder.cs index ee1bf2a05..72308da27 100644 --- a/src/Lidarr.Http/ClientSchema/SchemaBuilder.cs +++ b/src/Lidarr.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 Lidarr.Http.ClientSchema @@ -212,9 +213,9 @@ namespace Lidarr.Http.ClientSchema { 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 { @@ -230,9 +231,9 @@ namespace Lidarr.Http.ClientSchema { 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 { @@ -242,7 +243,18 @@ namespace Lidarr.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/Lidarr.Http/Extensions/NancyJsonSerializer.cs b/src/Lidarr.Http/Extensions/NancyJsonSerializer.cs index d6f5c3b65..e0ad0e3f6 100644 --- a/src/Lidarr.Http/Extensions/NancyJsonSerializer.cs +++ b/src/Lidarr.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 Lidarr.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 Lidarr.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/Lidarr.Http/Extensions/ReqResExtensions.cs b/src/Lidarr.Http/Extensions/ReqResExtensions.cs index 9b38a9d60..541f305fc 100644 --- a/src/Lidarr.Http/Extensions/ReqResExtensions.cs +++ b/src/Lidarr.Http/Extensions/ReqResExtensions.cs @@ -28,10 +28,8 @@ namespace Lidarr.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/Lidarr.Http/Lidarr.Http.csproj b/src/Lidarr.Http/Lidarr.Http.csproj index 856123f0e..b1d77e5cf 100644 --- a/src/Lidarr.Http/Lidarr.Http.csproj +++ b/src/Lidarr.Http/Lidarr.Http.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Lidarr.Http/REST/RestModule.cs b/src/Lidarr.Http/REST/RestModule.cs index b1a945499..bb2161787 100644 --- a/src/Lidarr.Http/REST/RestModule.cs +++ b/src/Lidarr.Http/REST/RestModule.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using FluentValidation; using FluentValidation.Results; using Lidarr.Http.Extensions; using Nancy; using Nancy.Responses.Negotiation; -using Newtonsoft.Json; using NzbDrone.Core.Datastore; namespace Lidarr.Http.REST @@ -248,9 +248,9 @@ namespace Lidarr.Http.REST { 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/Lidarr.Http/REST/RestResource.cs b/src/Lidarr.Http/REST/RestResource.cs index 39742997e..4774d10a5 100644 --- a/src/Lidarr.Http/REST/RestResource.cs +++ b/src/Lidarr.Http/REST/RestResource.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Lidarr.Http.REST { public abstract class RestResource { - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public int Id { get; set; } [JsonIgnore] diff --git a/src/NzbDrone.Common/Lidarr.Common.csproj b/src/NzbDrone.Common/Lidarr.Common.csproj index 58b7b75d9..3fcbf164a 100644 --- a/src/NzbDrone.Common/Lidarr.Common.csproj +++ b/src/NzbDrone.Common/Lidarr.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 @@ 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..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 85bd8f5f9..d7e75d490 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,9 +23,8 @@ namespace NzbDrone.Core.Datastore.Converters }; serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true)); - serializerSettings.Converters.Add(new KeyValuePairConverter()); /* Remove in .NET 5 */ - 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 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 c5dbde360..0a05c449a 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; namespace NzbDrone.Core.Datastore { @@ -13,6 +14,7 @@ namespace NzbDrone.Core.Datastore /// Allows a field to be lazy loaded. /// /// + [JsonConverter(typeof(LazyLoadedConverterFactory))] public class LazyLoaded : ILazyLoaded { protected TChild _value; @@ -60,11 +62,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/Lidarr.Core.csproj b/src/NzbDrone.Core/Lidarr.Core.csproj index 962dc2187..09676de0e 100644 --- a/src/NzbDrone.Core/Lidarr.Core.csproj +++ b/src/NzbDrone.Core/Lidarr.Core.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/NzbDrone.Core/Messaging/Commands/Command.cs b/src/NzbDrone.Core/Messaging/Commands/Command.cs index ffa579af9..c3fd74616 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/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 @@ 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 = serializerSettings; } diff --git a/src/NzbDrone.Host/Lidarr.Host.csproj b/src/NzbDrone.Host/Lidarr.Host.csproj index e02e1ccb8..52c07d9a8 100644 --- a/src/NzbDrone.Host/Lidarr.Host.csproj +++ b/src/NzbDrone.Host/Lidarr.Host.csproj @@ -12,7 +12,6 @@ - diff --git a/src/NzbDrone.Host/WebHost/WebHostController.cs b/src/NzbDrone.Host/WebHost/WebHostController.cs index 3407dad32..2e2f7f1b2 100644 --- a/src/NzbDrone.Host/WebHost/WebHostController.cs +++ b/src/NzbDrone.Host/WebHost/WebHostController.cs @@ -106,17 +106,15 @@ namespace NzbDrone.Host 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 951056a98..870aef74c 100644 --- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Integration.Test.Client throw response.ErrorException; } - AssertDisableCache(response.Headers); + AssertDisableCache(response); response.ErrorMessage.Should().BeNullOrWhiteSpace(); @@ -68,9 +68,10 @@ namespace NzbDrone.Integration.Test.Client 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 aadaa7dc1..edc1bdef2 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -27,6 +27,7 @@ using NzbDrone.SignalR; using NzbDrone.Test.Common; using NzbDrone.Test.Common.Categories; using RestSharp; +using RestSharp.Serializers.SystemTextJson; namespace NzbDrone.Integration.Test { @@ -98,6 +99,7 @@ namespace NzbDrone.Integration.Test 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); @@ -291,6 +293,7 @@ namespace NzbDrone.Integration.Test if (changed) { + result.NextAlbum = result.LastAlbum = null; Artist.Put(result); } 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 @@ namespace NzbDrone.SignalR 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/Lidarr.Test.Common.csproj b/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj index 37a60b57f..4dd3b0eae 100644 --- a/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/Lidarr.Test.Common.csproj @@ -9,7 +9,8 @@ - + +