2025-03-15 16:54:20 +03:30

82 lines
2.9 KiB
C#

using System.Collections.Concurrent;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace BuildingBlocks.EventStoreDB.Serialization;
public static class JsonObjectContractProvider
{
private static readonly Type ConstructorAttributeType = typeof(JsonConstructorAttribute);
private static readonly ConcurrentDictionary<string, JsonObjectContract> Constructors = new();
public static JsonObjectContract UsingNonDefaultConstructor(
JsonObjectContract contract,
Type objectType,
Func<ConstructorInfo, JsonPropertyCollection, IList<JsonProperty>> createConstructorParameters) =>
Constructors.GetOrAdd(objectType.AssemblyQualifiedName!, _ =>
{
var nonDefaultConstructor = GetNonDefaultConstructor(objectType);
if (nonDefaultConstructor == null)
return contract;
contract.OverrideCreator = GetObjectConstructor(nonDefaultConstructor);
contract.CreatorParameters.Clear();
foreach (var constructorParameter in
createConstructorParameters(nonDefaultConstructor, contract.Properties))
{
contract.CreatorParameters.Add(constructorParameter);
}
return contract;
});
private static ObjectConstructor<object> GetObjectConstructor(MethodBase method)
{
var c = method as ConstructorInfo;
if (c == null)
return a => method.Invoke(null, a)!;
if (!c.GetParameters().Any())
return _ => c.Invoke(Array.Empty<object?>());
return a => c.Invoke(a);
}
private static ConstructorInfo? GetNonDefaultConstructor(Type objectType)
{
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum)
return null;
return GetAttributeConstructor(objectType)
?? GetTheMostSpecificConstructor(objectType);
}
private static ConstructorInfo? GetAttributeConstructor(Type objectType)
{
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum)
return null;
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => c.GetCustomAttributes().Any(a => a.GetType() == ConstructorAttributeType)).ToList();
return constructors.Count switch
{
1 => constructors[0],
> 1 => throw new JsonException($"Multiple constructors with a {ConstructorAttributeType.Name}."),
_ => null
};
}
private static ConstructorInfo? GetTheMostSpecificConstructor(Type objectType) =>
objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderByDescending(e => e.GetParameters().Length)
.FirstOrDefault();
}