diff --git a/Magic.IndexedDb/Exceptions/MagicConstructorException.cs b/Magic.IndexedDb/Exceptions/MagicConstructorException.cs new file mode 100644 index 0000000..b6da8b0 --- /dev/null +++ b/Magic.IndexedDb/Exceptions/MagicConstructorException.cs @@ -0,0 +1,3 @@ +namespace Magic.IndexedDb.Exceptions; + +public class MagicConstructorException(string message) : Exception(message); \ No newline at end of file diff --git a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs index cc9750d..efec81d 100644 --- a/Magic.IndexedDb/Helpers/PropertyMappingCache.cs +++ b/Magic.IndexedDb/Helpers/PropertyMappingCache.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Reflection; using System.Text.Json.Serialization; +using Magic.IndexedDb.Exceptions; namespace Magic.IndexedDb.Helpers; @@ -37,12 +38,17 @@ public SearchPropEntry(Type type, Dictionary _proper EnforcePascalCase = false; } - // 🔥 Pick the best constructor: Prefer JsonConstructor, then fall back to a parameterized one, else fallback to parameterless - var jsonConstructor = constructors.FirstOrDefault(c => c.GetCustomAttribute() != null); - if (jsonConstructor == null) + // 🔥 Pick the best constructor: Prefer MagicConstructor, then fall back to a parameterized one, else fallback to parameterless + try + { + Constructor = constructors.SingleOrDefault(c => c.GetCustomAttribute() is not null); + } + catch (InvalidOperationException) { - Constructor = constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); + throw new MagicConstructorException("Only one magic constructor is allowed"); } + Constructor ??= constructors.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault(); + HasConstructorParameters = Constructor != null && Constructor.GetParameters().Length > 0; // 🔥 Cache constructor parameter mappings diff --git a/Magic.IndexedDb/Models/MagicContractResolver.cs b/Magic.IndexedDb/Models/MagicContractResolver.cs index 38de04b..6c16707 100644 --- a/Magic.IndexedDb/Models/MagicContractResolver.cs +++ b/Magic.IndexedDb/Models/MagicContractResolver.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; using Magic.IndexedDb.Helpers; @@ -62,25 +63,41 @@ internal class MagicContractResolver : JsonConverter private object CreateObjectFromDictionary(Type type, Dictionary propertyValues, SearchPropEntry search) { - // 🚀 If there's a constructor with parameters, use it - if (search.ConstructorParameterMappings.Count > 0) + object? obj = null; + List unInitializedProperties = new List(); + // If the constructor set in the SearchPropEntry contains parameters, fill them + if (search.HasConstructorParameters) { var constructorArgs = new object?[search.ConstructorParameterMappings.Count]; foreach (var (paramName, index) in search.ConstructorParameterMappings) { if (propertyValues.TryGetValue(paramName, out var value)) + { constructorArgs[index] = value; + propertyValues.Remove(paramName); + } else - constructorArgs[index] = GetDefaultValue(type.GetProperty(paramName)?.PropertyType ?? typeof(object)); + { + constructorArgs[index] = + GetDefaultValue(type.GetProperty(paramName)?.PropertyType ?? typeof(object)); + } } - return search.InstanceCreator(constructorArgs) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); + obj = search.InstanceCreator(constructorArgs); + } + else + { + // 🚀 Use parameterless constructor + obj = search.InstanceCreator([]); + } + + if (obj is null) + { + throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); } - // 🚀 Use parameterless constructor - var obj = search.InstanceCreator(Array.Empty()) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}."); - // 🚀 Assign property values + // 🚀 Assign property values (to properties not passed to constructor) foreach (var (propName, value) in propertyValues) { if (search.propertyEntries.TryGetValue(propName, out var propEntry)) diff --git a/Magic.IndexedDb/SchemaAnnotations/MagicConstructorAttribute.cs b/Magic.IndexedDb/SchemaAnnotations/MagicConstructorAttribute.cs new file mode 100644 index 0000000..9dd5430 --- /dev/null +++ b/Magic.IndexedDb/SchemaAnnotations/MagicConstructorAttribute.cs @@ -0,0 +1,6 @@ +namespace Magic.IndexedDb.SchemaAnnotations; + +/// +/// Sets the preferred constructor for serialization for MagicDB +/// +public class MagicConstructorAttribute : Attribute; \ No newline at end of file diff --git a/TestBase/Models/Person.cs b/TestBase/Models/Person.cs index 8e5afd3..1c7c5f9 100644 --- a/TestBase/Models/Person.cs +++ b/TestBase/Models/Person.cs @@ -14,7 +14,8 @@ public class Nested public class Person : MagicTableTool, IMagicTable { - [JsonConstructor] + + [MagicConstructor] public Person() { DoNotMapTest2 = "Test"; @@ -22,7 +23,7 @@ public Person() public Person(int _Id) { - DoNotMapTest2 = _Id.ToString(); + this._Id = _Id; } public List GetCompoundIndexes() =>