/// <summary>
        /// The <see cref="FromMap(Dictionary{string,AttributeValue},Type,DynamoSerializerOptions)"/> method converts a DynamoDB M attribute value to the type of the converter.
        /// </summary>
        /// <param name="value">The DynamoDB attribute value to convert.</param>
        /// <param name="targetType">The expected return type.</param>
        /// <param name="options">The deserialization options.</param>
        /// <returns>An instance of type <paramref name="targetType"/>.</returns>
        public override object?FromMap(Dictionary <string, AttributeValue> value, Type targetType, DynamoSerializerOptions options)
        {
            // create instance and set properties on it
            var result = Activator.CreateInstance(targetType) ?? throw new ApplicationException("Activator.CreateInstance() returned null");

            foreach (var property in targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
            {
                var propertyAttributes = property.GetCustomAttributes();

                // check if this object property should be ignored
                if (!(propertyAttributes.OfType <DynamoPropertyIgnoreAttribute>().SingleOrDefault() is null))
                {
                    continue;
                }

                // check if this object property has a custom name
                var name = propertyAttributes.OfType <DynamoPropertyNameAttribute>().SingleOrDefault()?.Name ?? property.Name;
                if (value.TryGetValue(name, out var attribute))
                {
                    property.SetValue(result, DynamoSerializer.Deserialize(attribute, property.PropertyType, options));
                }
                else
                {
                    property.SetValue(result, DynamoSerializer.Deserialize(attribute: null, property.PropertyType, options));
                }
            }
            return(result);
        }
        /// <summary>
        /// The <see cref="FromMap(Dictionary{string,AttributeValue},Type,DynamoSerializerOptions)"/> method converts a DynamoDB M attribute value to the type of the converter.
        /// </summary>
        /// <param name="value">The DynamoDB attribute value to convert.</param>
        /// <param name="targetType">The expected return type.</param>
        /// <param name="options">The deserialization options.</param>
        /// <returns>An instance of type <paramref name="targetType"/>.</returns>
        public override object?FromMap(Dictionary <string, AttributeValue> value, Type targetType, DynamoSerializerOptions options)
        {
            if (targetType.IsAssignableFrom(typeof(Dictionary <string, object>)))
            {
                return(value.ToDictionary(kv => kv.Key, kv => DynamoSerializer.Deserialize(kv.Value, typeof(object), options)));
            }

            // check if item type can be determined via `IDictionary<string, T>` inheritance
            var itemType = GetDictionaryItemType(targetType);

            // determine if a concrete type needs to be identified
            Type mapType = targetType;

            if (targetType.IsInterface)
            {
                if (!(itemType is null))
                {
                    mapType = typeof(Dictionary <,>).MakeGenericType(new[] { typeof(string), itemType });
                }
                else
                {
                    mapType = typeof(Dictionary <string, object>);
                }
                if (targetType.IsAssignableFrom(mapType))
                {
                    throw new DynamoSerializationException($"incompatible target type for M attribute value (given: {targetType.FullName})");
                }
            }
        /// <summary>
        /// The <see cref="FromList(List{AttributeValue},Type,DynamoSerializerOptions)"/> method converts a DynamoDB L attribute value to the type of the converter.
        /// </summary>
        /// <param name="value">The DynamoDB attribute value to convert.</param>
        /// <param name="targetType">The expected return type.</param>
        /// <param name="options">The deserialization options.</param>
        /// <returns>An instance of type <paramref name="targetType"/>.</returns>
        public override object?FromList(List <AttributeValue> value, Type targetType, DynamoSerializerOptions options)
        {
            if (targetType.IsAssignableFrom(typeof(List <object>)))
            {
                // NOTE (2021-06-23, bjorg): this covers the case where targetype is `IList`

                // return List<object>
                return(value.Select(item => DynamoSerializer.Deserialize(item, targetType: null, options)).ToList());
            }

            // check if item type can be determined via `IList<T>` inheritance
            var itemType = GetListItemType(targetType);

            // determine if a concrete type needs to be identified
            Type listType = targetType;

            if (targetType.IsInterface)
            {
                if (itemType != null)
                {
                    // create `List<T>`
                    listType = typeof(List <>).MakeGenericType(new[] { itemType });
                }
                else
                {
                    // create `ArrayList`
                    listType = typeof(ArrayList);
                }
                if (!targetType.IsAssignableFrom(listType))
                {
                    throw new DynamoSerializationException($"incompatible target type for L attribute value (given: {targetType.FullName})");
                }
            }

            // check if we can use the `IList` interface to add items to the list instance
            if (typeof(IList).IsAssignableFrom(listType))
            {
                var result = (IList)(Activator.CreateInstance(listType) ?? throw new ApplicationException("Activator.CreateInstance() returned null"));
                foreach (var item in value)
                {
                    result.Add(DynamoSerializer.Deserialize(item, itemType, options));
                }
                return(result);
            }
            else
            {
                // use `dynamic` to invoke the appropriate typed `Add()` method
                dynamic result = Activator.CreateInstance(listType) ?? throw new ApplicationException("Activator.CreateInstance() returned null");
                foreach (var item in value)
                {
                    result.Add(DynamoSerializer.Deserialize(item, itemType, options));
                }
                return(result);
            }
        }
 private object?DeserializeItem(Dictionary <string, AttributeValue> item, Type?type)
 => DynamoSerializer.Deserialize(item, type, SerializerOptions);
 internal TRecord?DeserializeItem <TRecord>(Dictionary <string, AttributeValue> item)
     where TRecord : class
 => DynamoSerializer.Deserialize <TRecord>(item, SerializerOptions);