ogomrub
ogomrub

Reputation: 146

Automapper convert list of system object

I need to convert one class to another using automapper and my objects looks like this:

public class Foo
{
    public List<object> Objects { get; set; }
}


public class Bar
{
    public List<object> Objects { get; set; }
}

public class FooItem
{
    public string Name { get; set; }
}

public class BarItem
{
    public string Name { get; set; }
}

There could be more types for items, not just FooItem and BarItem but I just use these two for simplicity and I need to have this design.

I've tried several things like type converters and so on but still no luck.

Here the current basic conversion code

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<FooItem, BarItem>();
    cfg.CreateMap<Foo, Bar>();
});

var foo = new Foo()
{
    Objects = new List<object>() { new FooItem() { Name = "name" } }
};

var map = Mapper.Map<Foo, Bar>(foo);

The goal is that the Bar object contains a list of BarItems at runtime, but so far I only have only managed to get a list of FooItem at runtime.

Any ideas?

Upvotes: 2

Views: 4515

Answers (3)

ogomrub
ogomrub

Reputation: 146

Inspired by @Ogglas I manage to found out an interesting solution where it is not needed to check for all the types.

Basically I wrote my own ITypeConverter

public class CustomResolver : ITypeConverter<List<object>, List<object>>
{
    public List<object> Convert(List<object> source, List<object> destination, ResolutionContext context)
    {
        var objects = new List<object>();

        foreach (var obj in source)
        {
            var destinationType = context.ConfigurationProvider.GetAllTypeMaps().First(x => x.SourceType == obj.GetType()).DestinationType;
            var target = context.Mapper.Map(obj, obj.GetType(), destinationType);
            objects.Add(target);
        }

        return objects;
    }
}

And get from the context the proper mapping for each type. Currently is not aware of nulls and so on...

Then, when configuring automapper

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<FooItem, BarItem>();
    cfg.CreateMap<List<object>, List<object>>().ConvertUsing<CustomResolver>();
    cfg.CreateMap<Foo, Bar>(); 
});

Upvotes: 0

Ogglas
Ogglas

Reputation: 70184

Are you satisfied with only a list of BarItems? In this case you can do it like this:

var map = Mapper.Map<IEnumerable<FooItem>, List<BarItem>>(foo.Objects.Cast<FooItem>());

Update: You can do it like this.

class Program
{
    static void Main(string[] args)
    {
        Mapper.Initialize(cfg =>
        {
            cfg.CreateMap<FooItem, BarItem>();
            cfg.CreateMap<Foo, Bar>()
            .ForMember(dest => dest.Objects, opt => opt.ResolveUsing<CustomResolver>());

        });
        Mapper.AssertConfigurationIsValid();
        var foo = new Foo()
        {
            Objects = new List<object>() { new FooItem() { Name = "name" } }
        };

        //var map = Mapper.Map<IEnumerable<FooItem>, List<BarItem>>(foo.Objects.Cast<FooItem>());
        var map = Mapper.Map<Foo, Bar>(foo);
    }
}

public class CustomResolver : IValueResolver<Foo, Bar, List<object>>
{ 
    public List<object> Resolve(Foo source, Bar destination, List<object> member, ResolutionContext context)
    {
        var map = Mapper.Map<IEnumerable<FooItem>, List<BarItem>>(source.Objects.Cast<FooItem>());
        return map.Cast<object>().ToList();
    }
}

Upvotes: 3

Georg Patscheider
Georg Patscheider

Reputation: 9463

You can do type checks inside a ConstructUsing clause:

cfg.CreateMap<object, object>()
    .ConstructUsing(src => {
         if (src is FooItem) {
             return Mapper.Map<BarItem>(src);
         }
         // ...
         throw new InvalidOperationException($"Can not map source item of type '{src.GetType().FullName}'.");
     });

But you probably need to introduce an interface for the items in the Objects collection, because the map object -> object overrides all other maps, but we want to use the map FooItem -> BarItem.

Upvotes: 2

Related Questions