Reworking the json serializer/deserializer to support the api's edit

This commit is contained in:
Zoe Roux 2020-11-01 04:35:08 +01:00
parent 0f5cc8f3a1
commit fb907509cb
26 changed files with 71 additions and 55 deletions

View File

@ -16,7 +16,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" /> <PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -8,4 +8,7 @@ namespace Kyoo.Models.Attributes
{ {
void OnMerge(object merged); void OnMerge(object merged);
} }
public class JsonReadOnly : Attribute { }
public class JsonIgnore : JsonReadOnly { }
} }

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,5 +1,5 @@
using Newtonsoft.Json; using System.Collections.Generic;
using System.Collections.Generic; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,5 +1,4 @@
using Newtonsoft.Json; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;

View File

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using Kyoo.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
{ {
@ -12,7 +11,7 @@ namespace Kyoo.Models
public string Poster { get; set; } public string Poster { get; set; }
public virtual IEnumerable<MetadataID> ExternalIDs { get; set; } public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public virtual IEnumerable<PeopleRole> Roles { get; set; } [JsonReadOnly] public virtual IEnumerable<PeopleRole> Roles { get; set; }
public People() {} public People() {}

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,5 +1,4 @@
using Newtonsoft.Json; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
@ -29,9 +28,9 @@ namespace Kyoo.Models
[JsonIgnore] public int? StudioID { get; set; } [JsonIgnore] public int? StudioID { get; set; }
[JsonIgnore] public virtual Studio Studio { get; set; } [JsonReadOnly] public virtual Studio Studio { get; set; }
[JsonIgnore] public virtual IEnumerable<Genre> Genres { get; set; } [JsonReadOnly] public virtual IEnumerable<Genre> Genres { get; set; }
[JsonIgnore] public virtual IEnumerable<PeopleRole> People { get; set; } [JsonReadOnly] public virtual IEnumerable<PeopleRole> People { get; set; }
[JsonIgnore] public virtual IEnumerable<Season> Seasons { get; set; } [JsonIgnore] public virtual IEnumerable<Season> Seasons { get; set; }
[JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; } [JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; }
[JsonIgnore] public virtual IEnumerable<Library> Libraries { get; set; } [JsonIgnore] public virtual IEnumerable<Library> Libraries { get; set; }

View File

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,8 +1,8 @@
using Kyoo.Models.Watch; using Kyoo.Models.Watch;
using Newtonsoft.Json;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Kyoo.Models.Attributes;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Controllers; using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.Models.Watch; using Kyoo.Models.Watch;
using PathIO = System.IO.Path; using PathIO = System.IO.Path;

View File

@ -112,9 +112,13 @@ namespace Kyoo
object defaultValue = property.PropertyType.IsValueType object defaultValue = property.PropertyType.IsValueType
? Activator.CreateInstance(property.PropertyType) ? Activator.CreateInstance(property.PropertyType)
: null; : null;
if (value?.Equals(defaultValue) == false) if (value?.Equals(defaultValue) == false)
{
if (value is IEnumerable enumerable && !(value is string))
value = RunGenericMethod(typeof(Enumerable), "ToList", GetEnumerableType(enumerable), value);
property.SetValue(first, value); property.SetValue(first, value);
}
} }
if (first is IOnMerge merge) if (first is IOnMerge merge)

View File

@ -3,11 +3,11 @@ using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Attributes;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@ -24,7 +24,8 @@ namespace Kyoo.Controllers
_forceSerialize = new Dictionary<Type, HashSet<string>>(); _forceSerialize = new Dictionary<Type, HashSet<string>>();
} }
public JsonPropertySelector(Dictionary<Type, HashSet<string>> ignored, Dictionary<Type, HashSet<string>> forceSerialize) public JsonPropertySelector(Dictionary<Type, HashSet<string>> ignored,
Dictionary<Type, HashSet<string>> forceSerialize = null)
{ {
_ignored = ignored ?? new Dictionary<Type, HashSet<string>>(); _ignored = ignored ?? new Dictionary<Type, HashSet<string>>();
_forceSerialize = forceSerialize ?? new Dictionary<Type, HashSet<string>>(); _forceSerialize = forceSerialize ?? new Dictionary<Type, HashSet<string>>();
@ -58,15 +59,20 @@ namespace Kyoo.Controllers
{ {
JsonProperty property = base.CreateProperty(member, memberSerialization); JsonProperty property = base.CreateProperty(member, memberSerialization);
if (IsIgnored(property.DeclaringType, property.PropertyName)) if (IsSerializationForced(property.DeclaringType, property.PropertyName))
{
property.ShouldSerialize = i => true;
property.Ignored = false;
}
else if (IsIgnored(property.DeclaringType, property.PropertyName))
{ {
property.ShouldSerialize = i => false; property.ShouldSerialize = i => false;
property.Ignored = true; property.Ignored = true;
} }
else if (IsSerializationForced(property.DeclaringType, property.PropertyName)) else
{ {
property.ShouldSerialize = i => true; property.ShouldSerialize = i => member.GetCustomAttribute<JsonReadOnly>(true) == null;
property.Ignored = false; property.ShouldDeserialize = i => member.GetCustomAttribute<JsonIgnore>(true) == null;
} }
return property; return property;
} }
@ -89,7 +95,7 @@ namespace Kyoo.Controllers
}) })
}, },
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(), context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value)); new MvcOptions()));
} }
} }
} }

View File

@ -10,6 +10,7 @@ using Kyoo.CommonApi;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
@ -142,18 +143,30 @@ namespace Kyoo.Controllers
{ {
if (edited == null) if (edited == null)
throw new ArgumentNullException(nameof(edited)); throw new ArgumentNullException(nameof(edited));
T old = await Get(edited.Slug);
if (old == null) Database.ChangeTracker.LazyLoadingEnabled = false;
throw new ItemNotFound($"No resource found with the slug {edited.Slug}."); try
{
if (resetOld) T old = await Get(edited.ID);
Utility.Nullify(old);
Utility.Merge(old, edited); if (old == null)
await Validate(old); throw new ItemNotFound($"No resource found with the ID {edited.ID}.");
await Database.SaveChangesAsync();
return old; if (resetOld)
Utility.Nullify(old);
Utility.Complete(old, edited);
await Validate(old);
// TODO should fix this, new links & deleted links should be kept.
// TODO The changetracker has trash values now & values can't be listed before the validation (exception is thrown)
foreach (EntityEntry x in Database.ChangeTracker.Entries().Where(x => x.Entity != old))
x.State = EntityState.Detached;
await Database.SaveChangesAsync();
return old;
}
finally
{
Database.ChangeTracker.LazyLoadingEnabled = true;
}
} }
protected virtual Task Validate(T resource) protected virtual Task Validate(T resource)

View File

@ -16,6 +16,7 @@
<Authors>Anonymus-Raccoon</Authors> <Authors>Anonymus-Raccoon</Authors>
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl> <RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
<StartupObject>Kyoo.Program</StartupObject> <StartupObject>Kyoo.Program</StartupObject>
<LangVersion>default</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo.Models namespace Kyoo.Models
{ {

View File

@ -1,16 +1,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
using Newtonsoft.Json;
// TODO Remove every [JsonIgnore] tag from here once the serializer knows which property should be serialized.
namespace Kyoo.Models namespace Kyoo.Models
{ {
public class ShowDE : Show public class ShowDE : Show
{ {
[JsonIgnore] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; } [JsonReadOnly] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; }
[ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Genre))] [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Genre))]
public override IEnumerable<Genre> Genres public override IEnumerable<Genre> Genres
{ {
@ -18,7 +14,7 @@ namespace Kyoo.Models
set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList();
} }
[JsonIgnore] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; } [JsonReadOnly] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; }
[ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))] [ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))]
public override IEnumerable<Library> Libraries public override IEnumerable<Library> Libraries
{ {
@ -26,7 +22,7 @@ namespace Kyoo.Models
set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList();
} }
[JsonIgnore] [NotMergable] public virtual ICollection<CollectionLink> CollectionLinks { get; set; } [JsonReadOnly] [NotMergable] public virtual ICollection<CollectionLink> CollectionLinks { get; set; }
[ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Collection))] [ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Collection))]
public override IEnumerable<Collection> Collections public override IEnumerable<Collection> Collections
{ {

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore; using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;

View File

@ -40,7 +40,8 @@ namespace Kyoo
configuration.RootPath = "wwwroot"; configuration.RootPath = "wwwroot";
}); });
services.AddControllers().AddNewtonsoftJson(); services.AddControllers()
.AddNewtonsoftJson(x => x.SerializerSettings.ContractResolver = new JsonPropertySelector());
services.AddHttpClient(); services.AddHttpClient();
services.AddDbContext<DatabaseContext>(options => services.AddDbContext<DatabaseContext>(options =>

@ -1 +1 @@
Subproject commit 42af654da4f4ec45945336e6f1085d9bda8ed773 Subproject commit f2ee4854ab7531cb8cf38ed9fd3d2b97d1d04a97