JsonSerializer: Cleaning up fields handling

This commit is contained in:
Zoe Roux 2021-10-09 13:13:10 +02:00
parent fd50a2dedc
commit 1cd88a8bfe
5 changed files with 97 additions and 73 deletions

View File

@ -26,7 +26,6 @@ using Autofac.Extras.AttributeMetadata;
using Kyoo.Abstractions;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Core.Api;
using Kyoo.Core.Controllers;
using Kyoo.Core.Models.Options;
using Kyoo.Core.Tasks;
@ -35,11 +34,12 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Serilog;
using IMetadataProvider = Kyoo.Abstractions.Controllers.IMetadataProvider;
using JsonOptions = Kyoo.Core.Api.JsonOptions;
namespace Kyoo.Core
{
@ -67,20 +67,6 @@ namespace Kyoo.Core
{ "logging", null }
};
/// <summary>
/// The configuration to use.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary>
/// Create a new core module instance and use the given configuration.
/// </summary>
/// <param name="configuration">The configuration to use</param>
public CoreModule(IConfiguration configuration)
{
_configuration = configuration;
}
/// <inheritdoc />
public void Configure(ContainerBuilder builder)
{
@ -140,17 +126,13 @@ namespace Kyoo.Core
/// <inheritdoc />
public void Configure(IServiceCollection services)
{
Uri publicUrl = _configuration.GetPublicUrl();
services.AddTransient<IConfigureOptions<MvcNewtonsoftJsonOptions>, JsonOptions>();
services.AddMvcCore()
.AddNewtonsoftJson()
.AddDataAnnotations()
.AddControllersAsServices()
.AddApiExplorer()
.AddNewtonsoftJson(x =>
{
x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer(publicUrl);
x.SerializerSettings.Converters.Add(new PeopleRoleConverter());
})
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressMapClientErrors = true;

View File

@ -24,6 +24,7 @@ using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
@ -64,13 +65,13 @@ namespace Kyoo.Core.Api
type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type;
type = Utility.GetGenericDefinition(type, typeof(Page<>))?.GetGenericArguments()[0] ?? type;
context.HttpContext.Items["ResourceType"] = type.Name;
PropertyInfo[] properties = type.GetProperties()
.Where(x => x.GetCustomAttribute<LoadableRelationAttribute>() != null)
.ToArray();
if (fields.Count == 1 && fields.Contains("all"))
{
fields = properties.Select(x => x.Name).ToList();
}
else
{
fields = fields
@ -82,10 +83,9 @@ namespace Kyoo.Core.Api
?.Name;
if (property != null)
return property;
context.Result = new BadRequestObjectResult(new
{
Error = $"{x} does not exist on {type.Name}."
});
context.Result = new BadRequestObjectResult(
new RequestError($"{x} does not exist on {type.Name}.")
);
return null;
})
.ToList();
@ -110,7 +110,7 @@ namespace Kyoo.Core.Api
if (result.DeclaredType == null)
return;
ILibraryManager library = context.HttpContext.RequestServices.GetService<ILibraryManager>();
ILibraryManager library = context.HttpContext.RequestServices.GetRequiredService<ILibraryManager>();
ICollection<string> fields = (ICollection<string>)context.HttpContext.Items["fields"];
Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>));
@ -119,13 +119,13 @@ namespace Kyoo.Core.Api
foreach (IResource resource in ((dynamic)result.Value).Items)
{
foreach (string field in fields!)
await library!.Load(resource, field);
await library.Load(resource, field);
}
}
else if (result.DeclaredType.IsAssignableTo(typeof(IResource)))
{
foreach (string field in fields!)
await library!.Load((IResource)result.Value, field);
await library.Load((IResource)result.Value, field);
}
}
}

View File

@ -0,0 +1,54 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace Kyoo.Core.Api
{
/// <summary>
/// The custom options of newtonsoft json. This simply add the <see cref="PeopleRoleConverter"/> and set
/// the <see cref="JsonSerializerContract"/>. It is on a separate class to use dependency injection.
/// </summary>
public class JsonOptions : IConfigureOptions<MvcNewtonsoftJsonOptions>
{
/// <summary>
/// The http context accessor given to the <see cref="JsonSerializerContract"/>.
/// </summary>
private readonly IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Create a new <see cref="JsonOptions"/>.
/// </summary>
/// <param name="httpContextAccessor">
/// The http context accessor given to the <see cref="JsonSerializerContract"/>.
/// </param>
public JsonOptions(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc />
public void Configure(MvcNewtonsoftJsonOptions options)
{
options.SerializerSettings.ContractResolver = new JsonSerializerContract(_httpContextAccessor);
options.SerializerSettings.Converters.Add(new PeopleRoleConverter());
}
}
}

View File

@ -16,27 +16,39 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Kyoo.Core.Api
{
public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver
/// <summary>
/// A custom json serializer that respects <see cref="SerializeIgnoreAttribute"/> and
/// <see cref="DeserializeIgnoreAttribute"/>. It also handle <see cref="LoadableRelationAttribute"/> via the
/// <c>fields</c> query parameter and <see cref="IThumbnails"/> items.
/// </summary>
public class JsonSerializerContract : CamelCasePropertyNamesContractResolver
{
private readonly Uri _host;
private int _depth = -1;
/// <summary>
/// The http context accessor used to retrieve the <c>fields</c> query parameter as well as the type of
/// resource currently serializing.
/// </summary>
private readonly IHttpContextAccessor _httpContextAccessor;
public JsonPropertyIgnorer(Uri host)
/// <summary>
/// Create a new <see cref="JsonSerializerContract"/>.
/// </summary>
/// <param name="httpContextAccessor">The http context accessor to use.</param>
public JsonSerializerContract(IHttpContextAccessor httpContextAccessor)
{
_host = host;
_httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc />
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
@ -44,19 +56,14 @@ namespace Kyoo.Core.Api
LoadableRelationAttribute relation = member.GetCustomAttribute<LoadableRelationAttribute>();
if (relation != null)
{
if (relation.RelationID == null)
property.ShouldSerialize = x => _depth == 0 && member.GetValue(x) != null;
else
property.ShouldSerialize = _ =>
{
property.ShouldSerialize = x =>
{
if (_depth != 0)
return false;
if (member.GetValue(x) != null)
return true;
return x.GetType().GetProperty(relation.RelationID)?.GetValue(x) != null;
};
}
string resType = (string)_httpContextAccessor.HttpContext!.Items["ResourceType"];
if (member.DeclaringType!.Name != resType)
return false;
ICollection<string> fields = (ICollection<string>)_httpContextAccessor.HttpContext!.Items["fields"];
return fields!.Contains(member.Name);
};
}
if (member.GetCustomAttribute<SerializeIgnoreAttribute>() != null)
@ -66,24 +73,10 @@ namespace Kyoo.Core.Api
// TODO use http context to disable serialize as.
// TODO check https://stackoverflow.com/questions/53288633/net-core-api-custom-json-resolver-based-on-request-values
SerializeAsAttribute serializeAs = member.GetCustomAttribute<SerializeAsAttribute>();
if (serializeAs != null)
property.ValueProvider = new SerializeAsProvider(serializeAs.Format, _host);
// SerializeAsAttribute serializeAs = member.GetCustomAttribute<SerializeAsAttribute>();
// if (serializeAs != null)
// property.ValueProvider = new SerializeAsProvider(serializeAs.Format, _host);
return property;
}
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
if (Utility.GetGenericDefinition(objectType, typeof(Page<>)) == null
&& !objectType.IsAssignableTo(typeof(IEnumerable))
&& objectType.Name != "AnnotatedProblemDetails")
{
contract.OnSerializingCallbacks.Add((_, _) => _depth++);
contract.OnSerializedCallbacks.Add((_, _) => _depth--);
}
return contract;
}
}
}

View File

@ -1,12 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>default</LangVersion>
<IsPackable>false</IsPackable>
<Authors>Zoe Roux</Authors>
<Company>SDG</Company>
</PropertyGroup>
<ItemGroup>
@ -37,5 +33,4 @@
<ProjectReference Include="../../src/Kyoo.Core/Kyoo.Core.csproj" />
<ProjectReference Include="../../src/Kyoo.TheTvdb/Kyoo.TheTvdb.csproj" />
</ItemGroup>
</Project>