Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derived to Derived type mapping #96

Closed
philippe-lavoie opened this issue Dec 14, 2016 · 5 comments
Closed

Derived to Derived type mapping #96

philippe-lavoie opened this issue Dec 14, 2016 · 5 comments

Comments

@philippe-lavoie
Copy link

I'm mapping classes from one namespace to another, however it looks like that mapster will mapped a derived class to the baseclass instead of mapping it to a derived class. For example, I have a class named Choice and ComplexChoice in both namespaces, if I pass a ComplexChoice as a property to a class, mapping it should also result in a ComplexChoice, the following code can be used to test this

namespace Contracts.Models.FakeOrder
{
    public class ValidateProductsRequest
    {
        public string Id { get; set; }
        public List<ProductItem> Items { get; set; }
    }

    public class ProductItem
    {
        public string ProductId { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
        public List<Choice> Choices { get; set; }
    }

    public class Choice
    {
        public ProductItem Item { get; set; }
        public string Instructions { get; set; }
    }

    public class ComplexChoice : Choice
    {
        public string OtherInstruction { get; set; }
    }
}

namespace Contracts.Models.FakeRestaurant
{

    public class ValidateProductsRequest
    {
        public string Id { get; set; }
        public List<ProductItem> Items { get; set; }
    }

    public class ProductItem
    {
        public string ProductId { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
        public List<Choice> Choices { get; set; }
    }

    public class Choice
    {
        public ProductItem Item { get; set; }
        public string Instructions { get; set; }
        public decimal ExtraCost { get; set; }
    }

    public class ComplexChoice : Choice
    {
        public string OtherInstruction { get; set; }
    }

}

 [TestClass]
    public class MapsterTest
    {

        public Contracts.Models.FakeOrder.ValidateProductsRequest GetRequest(string id, int nItems = 2, int nChoices = 1)
        {
            var result = new Contracts.Models.FakeOrder.ValidateProductsRequest();
            result.Id = id;
            result.Items = new List<Contracts.Models.FakeOrder.ProductItem>();
            for (int i = 0; i < nItems; i++)
            {
                var item = new Contracts.Models.FakeOrder.ProductItem();
                item.Price = i + 0.99m;
                item.ProductId = id + '-' + i.ToString();
                item.Quantity = i;
                item.Choices = new List<Contracts.Models.FakeOrder.Choice>();
                for (int j = 0; j < nChoices; j++)
                {
                    var choice = new Contracts.Models.FakeOrder.Choice();
                    choice.Item = new Contracts.Models.FakeOrder.ProductItem();
                    choice.Item.ProductId = "Sub" + item.ProductId;
                    choice.Item.Price = 0.15m;
                    choice.Instructions = "Whatever " + id;
                    item.Choices.Add(choice);
                }
                result.Items.Add(item);
            }
            return result;
        }

        [TestMethod]
        public void ValidateMapsterWithChildren()
        {
            TypeAdapterConfig<Contracts.Models.FakeOrder.Choice, Contracts.Models.FakeRestaurant.Choice>.NewConfig();
            TypeAdapterConfig<Contracts.Models.FakeOrder.ComplexChoice, Contracts.Models.FakeRestaurant.ComplexChoice>.NewConfig();
            TypeAdapterConfig<Contracts.Models.FakeOrder.ValidateProductsRequest, Contracts.Models.FakeRestaurant.ValidateProductsRequest>.NewConfig();

            var request = GetRequest("Something");
            var complexChoice = new Contracts.Models.FakeOrder.ComplexChoice();
            complexChoice.OtherInstruction = "Complex stuff";
            request.Items[0].Choices[0] = complexChoice;
            var mappedRequest = TypeAdapter.Adapt<Contracts.Models.FakeRestaurant.ValidateProductsRequest>(request);
            Assert.AreEqual(request.Id, mappedRequest.Id);
            Assert.AreEqual(request.Items.Count, mappedRequest.Items.Count);
            Assert.IsInstanceOfType(mappedRequest.Items[0].Choices[0], typeof(Contracts.Models.FakeRestaurant.ComplexChoice));
            Assert.AreEqual(request.Items[1].Choices[0].Item.ProductId, mappedRequest.Items[1].Choices[0].Item.ProductId);
        }
  }
}

If you could help, it would be greatly appreciated.

@philippe-lavoie
Copy link
Author

Doing the following would work, however I still think that there should be a way to configure how derived types should be mapped. Let me know if this is just 'out of scope'. Thanks

 public Contracts.Models.FakeRestaurant.Choice ChoiceChooser(Contracts.Models.FakeOrder.Choice from)
        {
            if(from == null)
            {
                return null;
            }

            switch(from.GetType().Name)
            {
                case "Choice":
                    return new Contracts.Models.FakeRestaurant.Choice();
                case "ComplexChoice":
                    return new Contracts.Models.FakeRestaurant.ComplexChoice();
                default:
                    throw new ArgumentException(nameof(from) + " is not of a valid type");
            }
        }

        [TestMethod]
        public void ValidateMapsterWithChildren()
        {
            TypeAdapterConfig<Contracts.Models.FakeOrder.Choice, Contracts.Models.FakeRestaurant.Choice>.NewConfig()
                .ConstructUsing(src => ChoiceChooser(src));
...

@chaowlert
Copy link
Collaborator

Hi, this is interesting feature, however we already capture in #52 and it still open, sorry for this. It is nice that you can find workaround. This will be my to do list (or any PR is welcome).

Thank you,

@philippe-lavoie
Copy link
Author

In my case, I'm doing code generation to bridge two code bases, so I just have to do some trickery to auto-add ConstructUsing. However, if I wasn't, manually generating 100 methods to manage custom construction would be problematic (yeah... it's a large code base)

I can perhaps look into a PR, might be easier to do then the code generation :)

@philippe-lavoie
Copy link
Author

Turns out that with the current code base, it really looks like everything is tied to a generic which will auto-downgrade to the actual instead of the dynamic derived type. In the case of something simple like

TestRequest
TestRequestChild: TestRequest
TestRequestGrandChild: TestRequestChild

I will have to auto-generate the following methods

using Mapster;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Testing.ExpectedResults
{
    public class RegisterEcpTesting2EcpInternalTesting
    {
        public InternalTesting.TestRequest RegisterFromTestRequest(Testing.TestRequest from)
        {
            if(from == null)
            {
                return null;
            }

            switch(from.GetType().Name)
            {
                case "TestRequest":
                    return new InternalTesting.TestRequest();
                case "TestRequestChild":
                    return new InternalTesting.TestRequestChild();
                case "TestRequestGrandChild":
                    return new InternalTesting.TestRequestGrandChild();
                default:
                    return new InternalTesting.TestRequest();
            }
        }

        public Testing.TestRequest RegisterToTestRequest(InternalTesting.TestRequest from)
        {
            if (from == null)
            {
                return null;
            }

            switch (from.GetType().Name)
            {
                case "TestRequest":
                    return new Testing.TestRequest();
                case "TestRequestChild":
                    return new Testing.TestRequestChild();
                case "TestRequestGrandChild":
                    return new Testing.TestRequestGrandChild();
                default:
                    return new Testing.TestRequest();
            }
        }

        public InternalTesting.TestRequestChild RegisterFromTestRequestChild(Testing.TestRequestChild from)
        {
            if (from == null)
            {
                return null;
            }

            switch (from.GetType().Name)
            {
                case "TestRequestChild":
                    return new InternalTesting.TestRequestChild();
                case "TestRequestGrandChild":
                    return new InternalTesting.TestRequestGrandChild();
                default:
                    return new InternalTesting.TestRequestChild();
            }
        }

        public Testing.TestRequestChild RegisterToTestRequestChild(InternalTesting.TestRequestChild from)
        {
            if (from == null)
            {
                return null;
            }

            switch (from.GetType().Name)
            {
                case "TestRequestChild":
                    return new Testing.TestRequestChild();
                case "TestRequestGrandChild":
                    return new Testing.TestRequestGrandChild();
                default:
                    return new Testing.TestRequestChild();
            }
        }

        public void Register()
        {
            TypeAdapterConfig<Testing.TestRequest, InternalTesting.TestRequest>.NewConfig()
                .ConstructUsing(src => RegisterFromTestRequest(src));
            TypeAdapterConfig<InternalTesting.TestRequest, Testing.TestRequest>.NewConfig()
                .ConstructUsing(src => RegisterToTestRequest(src));
            TypeAdapterConfig<Testing.TestRequestChild, InternalTesting.TestRequestChild>.NewConfig()
                .ConstructUsing(src => RegisterFromTestRequestChild(src));
            TypeAdapterConfig<InternalTesting.TestRequestChild, Testing.TestRequestChild>.NewConfig()
                .ConstructUsing(src => RegisterToTestRequestChild(src));
        }
    }
}

@chaowlert
Copy link
Collaborator

Hi, I added Include command in Mapster 3.0 (https://github.com/chaowlert/Mapster/wiki/Config-inheritance), please try. Thx u.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants