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

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
pull/5116/head
Qstick 3 years ago committed by Mark McDowall
parent 2e953a0eb1
commit f50b54b3f6

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
@ -38,12 +39,61 @@ namespace NzbDrone.Common.Serializer
public static T Deserialize<T>(string json)
where T : new()
{
return JsonConvert.DeserializeObject<T>(json, SerializerSettings);
try
{
return JsonConvert.DeserializeObject<T>(json, SerializerSettings);
}
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
}
public static object Deserialize(string json, Type type)
{
return JsonConvert.DeserializeObject(json, type, SerializerSettings);
try
{
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)
@ -77,10 +127,5 @@ namespace NzbDrone.Common.Serializer
Serializer.Serialize(jsonTextWriter, model);
jsonTextWriter.Flush();
}
public static void Serialize<TModel>(TModel model, Stream outputStream)
{
Serialize(model, new StreamWriter(outputStream));
}
}
}

@ -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; }

@ -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

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

@ -5,7 +5,6 @@
</PropertyGroup>
<ItemGroup>
<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="System.Text.Encoding.CodePages" Version="6.0.0" />
</ItemGroup>

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

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

@ -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.Common.Http;
using NzbDrone.Core.Messaging.Commands;

@ -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 Sonarr.Api.V3.CustomFilters
{
public string Type { get; set; }
public string Label { get; set; }
public List<dynamic> Filters { get; set; }
public List<ExpandoObject> Filters { get; set; }
}
public static class CustomFilterResourceMapper
@ -23,12 +24,12 @@ namespace Sonarr.Api.V3.CustomFilters
}
return new CustomFilterResource
{
Id = model.Id,
Type = model.Type,
Label = model.Label,
Filters = Json.Deserialize<List<dynamic>>(model.Filters)
};
{
Id = model.Id,
Type = model.Type,
Label = model.Label,
Filters = STJson.Deserialize<List<ExpandoObject>>(model.Filters)
};
}
public static CustomFilter ToModel(this CustomFilterResource resource)
@ -39,12 +40,12 @@ namespace Sonarr.Api.V3.CustomFilters
}
return new CustomFilter
{
Id = resource.Id,
Type = resource.Type,
Label = resource.Label,
Filters = Json.ToJson(resource.Filters)
};
{
Id = resource.Id,
Type = resource.Type,
Label = resource.Label,
Filters = STJson.ToJson(resource.Filters)
};
}
public static List<CustomFilterResource> ToResource(this IEnumerable<CustomFilter> filters)

@ -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.Languages;
@ -68,14 +68,10 @@ namespace Sonarr.Api.V3.Indexers
// Sent when queuing an unknown release
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
// [JsonIgnore]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int? SeriesId { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
// [JsonIgnore]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int? EpisodeId { get; set; }
}

@ -8,7 +8,6 @@
<PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Basic" 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" />
</ItemGroup>
<ItemGroup>

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

@ -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 Sonarr.Http.ClientSchema
@ -216,9 +217,9 @@ namespace Sonarr.Http.ClientSchema
{
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
{
@ -234,9 +235,9 @@ namespace Sonarr.Http.ClientSchema
{
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
{
@ -246,7 +247,18 @@ namespace Sonarr.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);
};
}
}

@ -1,5 +1,6 @@
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 Sonarr.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 Sonarr.Http.Extensions
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; }

@ -28,10 +28,8 @@ namespace Sonarr.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<TModel> AsResponse<TModel>(this TModel model, NancyContext context, HttpStatusCode statusCode = HttpStatusCode.OK)

@ -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 Sonarr.Http.Extensions;
@ -233,7 +233,7 @@ namespace Sonarr.Http.REST
{
resource = Request.Body.FromJson<TResource>();
}
catch (JsonReaderException e)
catch (JsonException e)
{
throw new BadRequestException($"Invalid request body. {e.Message}");
}
@ -330,7 +330,6 @@ namespace Sonarr.Http.REST
}
// v3 uses filters in key=value format
foreach (var key in Request.Query)
{
if (_excludedKeys.Contains(key))
@ -339,10 +338,10 @@ namespace Sonarr.Http.REST
}
pagingResource.Filters.Add(new PagingResourceFilter
{
Key = key,
Value = Request.Query[key]
});
{
Key = key,
Value = Request.Query[key]
});
}
return pagingResource;

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

@ -7,7 +7,6 @@
<PackageReference Include="Nancy" Version="2.0.0" />
<PackageReference Include="Nancy.Authentication.Basic" 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" />
</ItemGroup>
<ItemGroup>

Loading…
Cancel
Save