New: Use System.Text.Json for Nancy and SignalR

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
This commit is contained in:
Qstick 2021-08-08 13:35:41 -04:00 committed by Mark McDowall
parent 2e953a0eb1
commit f50b54b3f6
18 changed files with 114 additions and 62 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Reflection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@ -37,14 +38,63 @@ namespace NzbDrone.Common.Serializer
public static T Deserialize<T>(string json) public static T Deserialize<T>(string json)
where T : new() where T : new()
{
try
{ {
return JsonConvert.DeserializeObject<T>(json, SerializerSettings); return JsonConvert.DeserializeObject<T>(json, SerializerSettings);
} }
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
}
public static object Deserialize(string json, Type type) public static object Deserialize(string json, Type type)
{
try
{ {
return JsonConvert.DeserializeObject(json, type, SerializerSettings); return JsonConvert.DeserializeObject(json, type, SerializerSettings);
} }
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
}
private static JsonReaderException DetailedJsonReaderException(JsonReaderException ex, string json)
{
var lineNumber = ex.LineNumber == 0 ? 0 : (ex.LineNumber - 1);
var linePosition = ex.LinePosition;
var lines = json.Split('\n');
if (lineNumber >= 0 && lineNumber < lines.Length &&
linePosition >= 0 && linePosition < lines[lineNumber].Length)
{
var line = lines[lineNumber];
var start = Math.Max(0, linePosition - 20);
var end = Math.Min(line.Length, linePosition + 20);
var snippetBefore = line.Substring(start, linePosition - start);
var snippetAfter = line.Substring(linePosition, end - linePosition);
var message = ex.Message + " (Json snippet '" + snippetBefore + "<--error-->" + snippetAfter + "')";
// Not risking updating JSON.net from 9.x to 10.x just to get this as public ctor.
var ctor = typeof(JsonReaderException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(Exception), typeof(string), typeof(int), typeof(int) }, null);
if (ctor != null)
{
return (JsonReaderException)ctor.Invoke(new object[] { message, ex, ex.Path, ex.LineNumber, linePosition });
}
// JSON.net 10.x ctor in case we update later.
ctor = typeof(JsonReaderException).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string), typeof(int), typeof(int), typeof(Exception) }, null);
if (ctor != null)
{
return (JsonReaderException)ctor.Invoke(new object[] { message, ex.Path, ex.LineNumber, linePosition, ex });
}
}
return ex;
}
public static bool TryDeserialize<T>(string json, out T result) public static bool TryDeserialize<T>(string json, out T result)
where T : new() where T : new()
@ -77,10 +127,5 @@ namespace NzbDrone.Common.Serializer
Serializer.Serialize(jsonTextWriter, model); Serializer.Serialize(jsonTextWriter, model);
jsonTextWriter.Flush(); jsonTextWriter.Flush();
} }
public static void Serialize<TModel>(TModel model, Stream outputStream)
{
Serialize(model, new StreamWriter(outputStream));
}
} }
} }

View File

@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json.Serialization;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
@ -9,7 +9,7 @@ namespace NzbDrone.Core.Profiles.Qualities
{ {
public class QualityProfileQualityItem : IEmbeddedDocument public class QualityProfileQualityItem : IEmbeddedDocument
{ {
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }

View File

@ -1,6 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json.Serialization;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Qualities namespace NzbDrone.Core.Qualities

View File

@ -11,8 +11,7 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.ThingiProvider namespace NzbDrone.Core.ThingiProvider
{ {
public class ProviderRepository<TProviderDefinition> : BasicRepository<TProviderDefinition>, IProviderRepository<TProviderDefinition> public class ProviderRepository<TProviderDefinition> : BasicRepository<TProviderDefinition>, IProviderRepository<TProviderDefinition>
where TProviderDefinition : ProviderDefinition, where TProviderDefinition : ProviderDefinition, new()
new()
{ {
protected readonly JsonSerializerOptions _serializerSettings; protected readonly JsonSerializerOptions _serializerSettings;

View File

@ -5,7 +5,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="6.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Owin" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="6.0.0" />
<PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" /> <PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -108,9 +108,9 @@ namespace NzbDrone.Host
{ {
services services
.AddSignalR() .AddSignalR()
.AddNewtonsoftJsonProtocol(options => .AddJsonProtocol(options =>
{ {
options.PayloadSerializerSettings = Json.GetSerializerSettings(); options.PayloadSerializerOptions = STJson.GetSerializerSettings();
}); });
}) })
.Configure(app => .Configure(app =>

View File

@ -1,4 +1,3 @@
using Newtonsoft.Json;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
namespace NzbDrone.SignalR namespace NzbDrone.SignalR
@ -8,7 +7,7 @@ namespace NzbDrone.SignalR
public object Body { get; set; } public object Body { get; set; }
public string Name { get; set; } public string Name { get; set; }
[JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
public ModelAction Action { get; set; } public ModelAction Action { get; set; }
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json.Serialization;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Linq; using System.Linq;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
using NzbDrone.Core.CustomFilters; using NzbDrone.Core.CustomFilters;
@ -10,7 +11,7 @@ namespace Sonarr.Api.V3.CustomFilters
{ {
public string Type { get; set; } public string Type { get; set; }
public string Label { get; set; } public string Label { get; set; }
public List<dynamic> Filters { get; set; } public List<ExpandoObject> Filters { get; set; }
} }
public static class CustomFilterResourceMapper public static class CustomFilterResourceMapper
@ -27,7 +28,7 @@ namespace Sonarr.Api.V3.CustomFilters
Id = model.Id, Id = model.Id,
Type = model.Type, Type = model.Type,
Label = model.Label, Label = model.Label,
Filters = Json.Deserialize<List<dynamic>>(model.Filters) Filters = STJson.Deserialize<List<ExpandoObject>>(model.Filters)
}; };
} }
@ -43,7 +44,7 @@ namespace Sonarr.Api.V3.CustomFilters
Id = resource.Id, Id = resource.Id,
Type = resource.Type, Type = resource.Type,
Label = resource.Label, Label = resource.Label,
Filters = Json.ToJson(resource.Filters) Filters = STJson.ToJson(resource.Filters)
}; };
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using System.Text.Json.Serialization;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.Languages; using NzbDrone.Core.Languages;
@ -68,14 +68,10 @@ namespace Sonarr.Api.V3.Indexers
// Sent when queuing an unknown release // Sent when queuing an unknown release
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
// [JsonIgnore]
public int? SeriesId { get; set; } public int? SeriesId { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
// [JsonIgnore]
public int? EpisodeId { get; set; } public int? EpisodeId { get; set; }
} }

View File

@ -8,7 +8,6 @@
<PackageReference Include="Nancy" Version="2.0.0" /> <PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Basic" Version="2.0.0" /> <PackageReference Include="Nancy.Authentication.Basic" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Forms" Version="2.0.0" /> <PackageReference Include="Nancy.Authentication.Forms" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.14" /> <PackageReference Include="NLog" Version="4.7.14" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Core.Update; using NzbDrone.Core.Update;
using Sonarr.Http.REST; using Sonarr.Http.REST;
@ -9,7 +8,6 @@ namespace Sonarr.Api.V3.Update
{ {
public class UpdateResource : RestResource public class UpdateResource : RestResource
{ {
[JsonConverter(typeof(Newtonsoft.Json.Converters.VersionConverter))]
public Version Version { get; set; } public Version Version { get; set; }
public string Branch { get; set; } public string Branch { get; set; }

View File

@ -2,10 +2,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Newtonsoft.Json.Linq; using System.Text.Json;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Reflection; using NzbDrone.Common.Reflection;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
namespace Sonarr.Http.ClientSchema namespace Sonarr.Http.ClientSchema
@ -216,9 +217,9 @@ namespace Sonarr.Http.ClientSchema
{ {
return Enumerable.Empty<int>(); return Enumerable.Empty<int>();
} }
else if (fieldValue.GetType() == typeof(JArray)) else if (fieldValue is JsonElement e && e.ValueKind == JsonValueKind.Array)
{ {
return ((JArray)fieldValue).Select(s => s.Value<int>()); return e.EnumerateArray().Select(s => s.GetInt32());
} }
else else
{ {
@ -234,9 +235,9 @@ namespace Sonarr.Http.ClientSchema
{ {
return Enumerable.Empty<string>(); return Enumerable.Empty<string>();
} }
else if (fieldValue.GetType() == typeof(JArray)) else if (fieldValue is JsonElement e && e.ValueKind == JsonValueKind.Array)
{ {
return ((JArray)fieldValue).Select(s => s.Value<string>()); return e.EnumerateArray().Select(s => s.GetString());
} }
else else
{ {
@ -246,7 +247,18 @@ namespace Sonarr.Http.ClientSchema
} }
else 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);
};
} }
} }

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.Json;
using Nancy; using Nancy;
using Nancy.Responses.Negotiation; using Nancy.Responses.Negotiation;
using NzbDrone.Common.Serializer; using NzbDrone.Common.Serializer;
@ -8,6 +9,13 @@ namespace Sonarr.Http.Extensions
{ {
public class NancyJsonSerializer : ISerializer public class NancyJsonSerializer : ISerializer
{ {
protected readonly JsonSerializerOptions _serializerSettings;
public NancyJsonSerializer()
{
_serializerSettings = STJson.GetSerializerSettings();
}
public bool CanSerialize(MediaRange contentType) public bool CanSerialize(MediaRange contentType)
{ {
return contentType == "application/json"; return contentType == "application/json";
@ -15,7 +23,7 @@ namespace Sonarr.Http.Extensions
public void Serialize<TModel>(MediaRange contentType, TModel model, Stream outputStream) public void Serialize<TModel>(MediaRange contentType, TModel model, Stream outputStream)
{ {
Json.Serialize(model, outputStream); STJson.Serialize(model, outputStream, _serializerSettings);
} }
public IEnumerable<string> Extensions { get; private set; } public IEnumerable<string> Extensions { get; private set; }

View File

@ -28,10 +28,8 @@ namespace Sonarr.Http.Extensions
public static object FromJson(this Stream body, Type type) public static object FromJson(this Stream body, Type type)
{ {
var reader = new StreamReader(body, true);
body.Position = 0; body.Position = 0;
var value = reader.ReadToEnd(); return STJson.Deserialize(body, type);
return Json.Deserialize(value, type);
} }
public static JsonResponse<TModel> AsResponse<TModel>(this TModel model, NancyContext context, HttpStatusCode statusCode = HttpStatusCode.OK) public static JsonResponse<TModel> AsResponse<TModel>(this TModel model, NancyContext context, HttpStatusCode statusCode = HttpStatusCode.OK)

View File

@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json;
using FluentValidation; using FluentValidation;
using FluentValidation.Results; using FluentValidation.Results;
using Nancy; using Nancy;
using Nancy.Responses.Negotiation; using Nancy.Responses.Negotiation;
using Newtonsoft.Json;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using Sonarr.Http.Extensions; using Sonarr.Http.Extensions;
@ -233,7 +233,7 @@ namespace Sonarr.Http.REST
{ {
resource = Request.Body.FromJson<TResource>(); resource = Request.Body.FromJson<TResource>();
} }
catch (JsonReaderException e) catch (JsonException e)
{ {
throw new BadRequestException($"Invalid request body. {e.Message}"); throw new BadRequestException($"Invalid request body. {e.Message}");
} }
@ -330,7 +330,6 @@ namespace Sonarr.Http.REST
} }
// v3 uses filters in key=value format // v3 uses filters in key=value format
foreach (var key in Request.Query) foreach (var key in Request.Query)
{ {
if (_excludedKeys.Contains(key)) if (_excludedKeys.Contains(key))

View File

@ -1,11 +1,11 @@
using Newtonsoft.Json; using System.Text.Json.Serialization;
namespace Sonarr.Http.REST namespace Sonarr.Http.REST
{ {
public abstract class RestResource public abstract class RestResource
{ {
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int Id { get; set; } public virtual int Id { get; set; }
[JsonIgnore] [JsonIgnore]
public virtual string ResourceName => GetType().Name.ToLowerInvariant().Replace("resource", ""); public virtual string ResourceName => GetType().Name.ToLowerInvariant().Replace("resource", "");

View File

@ -7,7 +7,6 @@
<PackageReference Include="Nancy" Version="2.0.0" /> <PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Basic" Version="2.0.0" /> <PackageReference Include="Nancy.Authentication.Basic" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Forms" Version="2.0.0" /> <PackageReference Include="Nancy.Authentication.Forms" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.14" /> <PackageReference Include="NLog" Version="4.7.14" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>