I am mapping from a JObject to a custom class and it works fine, but i'd rather tell it to do this default type of mapping once instead of having to do a .MapFrom for every property. Most of the source "properties" are just the lowercase underscored version of the Pascal case property names on the destination.
Since JObject doesnt have the values I want as properties, i have to index into them via the MapFrom (I can't use SourceMemberNamingConvention/DestinationMemberNamingConvention). So I want an expression that i can use via something like ForAllOtherMembers to apply this default value retrieval from the JObject but I can't it to work...what options do i have?
I have tried using ResolveUsing method that I would have to also use ConstructedBy method but ResolveUsing returns a void (now? in version 5) so I couldn't do a .ConstructedBy() on it.
Below I am having to do each MapFrom even though they follow the same access pattern:
MapperConfiguration MapperConfig = new MapperConfiguration(cfg => {
cfg.CreateMap<string, bool>().ConvertUsing<BooleanTypeConverter>();
cfg.CreateMap<JObject, FiscalYear>()
.ForMember("EmployeeName",
options => options.MapFrom(jo => jo["employee_name"]))
.ForMember("YearName",
options => options.MapFrom(jo => jo["year_name"]));
UPDATE: I went ahead and just did it more the manual way and replaced strings with a delegate
public static class AutoMapperConfig
{
public static MapperConfiguration MapperConfig = new MapperConfiguration(cfg => {
cfg.CreateMap<string, bool>().ConvertUsing<BooleanTypeConverter>();
cfg.CreateMap<JToken, FiscalYear>()
.Map(d => d.EmployeeName)
.Map(d => d.Year, "int_year")
.Map(d => d.Name, "year");
});
}
public static class MappingExpressionExtensions
{
private static readonly MatchEvaluator ToSnakeCaseEvaluator = m =>
{
string match = m.ToString();
return "_" + char.ToLower(match[0]) + match.Substring(1);
};
public static IMappingExpression<JToken, TDest> Map<TDest, TDestProp>(this IMappingExpression<JToken, TDest> map,
Expression<Func<TDest, TDestProp>> propertyExpression, string sourceName = null)
{
var propertyName = propertyExpression.PropertyName();
sourceName = string.IsNullOrWhiteSpace(sourceName)
? new Regex("([A-Z])").Replace(propertyName, ToSnakeCaseEvaluator).TrimStart('_')
: sourceName;
var propType = typeof(TDestProp);
if (propType == typeof(bool))
{
// in order for BooleanTypeConverter to work, convert to string and run as a normal mapping
map.ForMember(propertyExpression.PropertyName(), o => o.MapFrom(jo => jo[sourceName].ToString()));
return map;
}
var isNullableGenericType = propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(Nullable<>);
var underlyingType = isNullableGenericType ? Nullable.GetUnderlyingType(propType) : propType;
TypeConverter converter = TypeDescriptor.GetConverter(underlyingType);
map.ForMember(propertyName,
o => o.MapFrom(jo => (isNullableGenericType && string.IsNullOrWhiteSpace(jo[sourceName].ToString()))
|| (IsNumeric(jo[sourceName]) && string.IsNullOrWhiteSpace(jo[sourceName].ToString()))
? 0 : converter.ConvertFrom(jo[sourceName].ToString())));
return map;
}
public static bool IsNumeric(object expression)
{
if (expression == null)
return false;
double number;
return Double.TryParse(Convert.ToString(expression, CultureInfo.InvariantCulture),
NumberStyles.Any, NumberFormatInfo.InvariantInfo, out number);
}
}
public class BooleanTypeConverter : ITypeConverter<string, bool>
{
/// <summary>
/// Automapper version compatible with version 4.x
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public bool Convert(ResolutionContext context)
{
switch (context.SourceValue.ToString().ToLower().Trim())
{
case "true":
case "yes":
case "y":
case "1":
return true;
}
return false;
}
//// Automapper version compatible with version 5.0+
//public bool Convert(string source, bool destination, ResolutionContext context)
//{
// switch (source.ToLower().Trim())
// {
// case "true":
// case "yes":
// case "y":
// case "1":
// return true;
// }
// return false;
//}
// put this in static class
public static string PropertyName<T, TProperty>(this Expression<Func<T, TProperty>> property)
{
if (property.Body is MemberExpression)
return ((MemberExpression) property.Body).Member.Name;
return ((MemberExpression) ((UnaryExpression) property.Body).Operand).Member.Name;
}
}