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 Constructors = new(); public static JsonObjectContract UsingNonDefaultConstructor( JsonObjectContract contract, Type objectType, Func> 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 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()); 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(); }