Skip to content

BuildAdapter().AdaptToType<T>() does not include all expected properties when source is using inheritance #776

@crates-barrels

Description

@crates-barrels

While rewriting some code to use the async implementation of adapt using package Mapster.Async v2.0.1, I stubled upon an issue where not all expected source properties were copied to the result class.
However, the code was working as expected when I was using .Adapt<T>().

I've tracked it down to the source class being inherited from a base class and using a method where the parameter is of this base class type.
The following code can be used (e.g. in a console application) to reproduce the issue and show the difference in behavior between .Adapt<T>() and AdaptToType<T>():

using Mapster;
using Newtonsoft.Json;

namespace MapsterBug
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // sample source object
            var myImplementation = new MyImplementation
            {
                InterfaceProperty = 123,
                FirstImplementationProperty = 789,
                SecondImplementationProperty = "test"
            };

            // adapt using .Adapt<T>(): the result is as expected
            var resultAdapt = myImplementation.Adapt<MyDto>();

            Console.WriteLine("Result of myImplementation.Adapt<MyDto>():");
            Console.WriteLine(JsonConvert.SerializeObject(resultAdapt, Formatting.Indented));
            Console.WriteLine("\r\n-----------------------------------------\r\n");

            // adapt using .AdaptToType<T>(): the result is as expected
            var resultAdaptToType = myImplementation.BuildAdapter().AdaptToType<MyDto>();

            Console.WriteLine("Result of myImplementation.BuildAdapter().AdaptToType<MyDto>():");
            Console.WriteLine(JsonConvert.SerializeObject(resultAdaptToType, Formatting.Indented));
            Console.WriteLine("\r\n-----------------------------------------\r\n");

            // adapt using .Adapt<T>() inside a method where the parameter is of the interface type: the result is as expected
            var resultAdaptWithInterface = AdaptWithInterface(myImplementation);

            Console.WriteLine("Result of myImplementation.Adapt<MyDto>() in method with interface:");
            Console.WriteLine(JsonConvert.SerializeObject(resultAdaptWithInterface, Formatting.Indented));
            Console.WriteLine("\r\n-----------------------------------------\r\n");

            // adapt using .AdaptToType<T>() inside a method where the parameter is of the interface type:
            // the result only includes the interface property, but it does not include the properties of the implementation
            var resultAdaptToTypeWithInterface = AdaptToTypeWithInterface(myImplementation);

            Console.WriteLine("Result of myInterface.BuildAdapter().AdaptToType<MyDto>() in method with interface:");
            Console.WriteLine(JsonConvert.SerializeObject(resultAdaptToTypeWithInterface, Formatting.Indented));
        }

        static MyDto AdaptWithInterface(IMyInterface myInterface) => myInterface.Adapt<MyDto>();
        static MyDto AdaptToTypeWithInterface(IMyInterface myInterface) => myInterface.BuildAdapter().AdaptToType<MyDto>();
    }

    public interface IMyInterface
    {
        public int InterfaceProperty { get; set; }
    }

    public class MyImplementation : IMyInterface
    {
        public int InterfaceProperty { get; set; }
        public int FirstImplementationProperty { get; set; }
        public string SecondImplementationProperty { get; set; }
    }

    public class MyDto
    {
        public int InterfaceProperty { get; set; }
        public int FirstImplementationProperty { get; set; }
        public string SecondImplementationProperty { get; set; }
    }
}

This code is using AdaptToType<T>() to not complicate things by using an additional package (Mapster.Async), but the AdaptToTypeAsync<T>() method has the same behavior.
When running the application, the output is as follows:

Result of myImplementation.Adapt<MyDto>():
{
  "InterfaceProperty": 123,
  "FirstImplementationProperty": 789,
  "SecondImplementationProperty": "test"
}

-----------------------------------------

Result of myImplementation.BuildAdapter().AdaptToType<MyDto>():
{
  "InterfaceProperty": 123,
  "FirstImplementationProperty": 789,
  "SecondImplementationProperty": "test"
}

-----------------------------------------

Result of myImplementation.Adapt<MyDto>() in method with interface:
{
  "InterfaceProperty": 123,
  "FirstImplementationProperty": 789,
  "SecondImplementationProperty": "test"
}

-----------------------------------------

Result of myInterface.BuildAdapter().AdaptToType<MyDto>() in method with interface:
{
  "InterfaceProperty": 123,
  **"FirstImplementationProperty": 0,**
  **"SecondImplementationProperty": null**
}

Results 1, 2 and 3 are as expected, but the last result only includes the IMyInterface property (InterfaceProperty) and not the additional MyImplementation properties (FirstImplementationProperty and SecondImplementationProperty) marked in bold.

Versions used in code sample:

  • .NET 8
  • Mapster 7.4.0
  • Newtonsoft.Json 13.0.3

Sub-issues

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions