MapsterMapper / MapsterMapper/Mapster

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

Open
#776 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug configuration problem waiting for info
Dominant language
C#
Stars
5.2k
Forks
410
Avg merge
2d 12h
Merged PRs (30d)
6

Description

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()`.

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()` and `AdaptToType()`:
```
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(): the result is as expected
var resultAdapt = myImplementation.Adapt();

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

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

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

// adapt using .Adapt() 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() in method with interface:");
Console.WriteLine(JsonConvert.SerializeObject(resultAdaptWithInterface, Formatting.Indented));
Console.WriteLine("\r\n-----------------------------------------\r\n");

// adapt using .AdaptToType() 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() in method with interface:");
Console.WriteLine(JsonConvert.SerializeObject(resultAdaptToTypeWithInterface, Formatting.Indented));
}

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

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()` to not complicate things by using an additional package (_Mapster.Async_), but the `AdaptToTypeAsync()` method has the same behavior.
When running the application, the output is as follows:
```
Result of myImplementation.Adapt():
{
"InterfaceProperty": 123,
"FirstImplementationProperty": 789,
"SecondImplementationProperty": "test"
}

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

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

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

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

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

Result of myInterface.BuildAdapter().AdaptToType() 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the difference between Adapt() and BuildAdapter().AdaptToType() using the console example, especially AdaptToTypeWithInterface with IMyInterface and MyImplementation. Trace the BuildAdapter().AdaptToType() entry point and compare its source-type handling with Adapt(); done means inherited implementation properties are copied in both synchronous and asynchronous paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.