Conditional mapping with nested adapters fails for one type but works for another identical pattern
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 48/100
Hướng nghiên cứu
Bắt đầu với cấu hình InquiryResponseMapper.Register được cung cấp, sau đó tái hiện việc ánh xạ thông qua TypeAdapterConfig.Compile() và Mapper cho cả hai nhánh OneOf. So sánh việc ánh xạ có điều kiện của PlaintextCard và EncryptedCard với các adapter lồng nhau đã đăng ký tương ứng; hoàn tất khi PlaintextCard được điền thông qua adapter của nó mà không làm hỏng đường dẫn EncryptedCard đang hoạt động.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Conditional mapping with nested adapters fails for one type but works for another identical pattern
Summary
When using conditional mapping (3-parameter .Map() overload) with OneOf types, Mapster successfully applies nested adapters for EncryptedCard but fails to apply them for PlaintextCard, even though both use the exact same mapping pattern. The PlaintextCard property is set to null instead of being mapped through the registered adapter.
Environment
- Mapster Version: 7.4.0
- Mapster.Core Version: 1.2.1
- MapsterMapper Version: 1.0.2
- .NET Version: .NET 9.0
- Project Type: ASP.NET Core Web API with gRPC
Minimal Reproduction
Source Types
// OneOf discriminated union
[GenerateOneOf]
public partial class CardOf : OneOfBase<CardWithPlainTextPan, CardWithEncryptedPan>;
public record CardWithPlainTextPan(string Pan, string ExpiryYear, string ExpiryMonth);
public record CardWithEncryptedPan(
string EncryptedCardData,
string ExpiryYear,
string ExpiryMonth);
public record AccountUpdaterInquiryResponse(InquiryStatus Status, Maybe<CardOf> Card);
Destination Types (Protobuf Generated)
// From protobuf - simplified
public sealed partial class InquiryResponse
{
public Processing.Card.V2.Card PlaintextCard { get; set; }
public Processing.Card.V2.EncryptedCard EncryptedCard { get; set; }
public InquiryStatus Status { get; set; }
public enum AccountOneofCase { None, PlaintextCard, EncryptedCard }
}
public sealed partial class Card
{
public string Pan { get; set; }
public string ExpiryYear { get; set; }
public string ExpiryMonth { get; set; }
public string Cvc { get; set; } // Optional field
}
public sealed partial class EncryptedCard
{
public string JweCompactEncryptedData { get; set; }
public string ExpiryYear { get; set; }
public string ExpiryMonth { get; set; }
public EncryptedCardContentType EncryptedDataContentType { get; set; }
}
Mapper Configuration
public class InquiryResponseMapper : IRegister
{
public void Register(TypeAdapterConfig config)
{
// Nested adapter for PlaintextCard
config.NewConfig<CardWithPlainTextPan, Processing.Card.V2.Card>()
.Map(d => d.Pan, s => s.Pan)
.Map(d => d.ExpiryMonth, s => s.ExpiryMonth)
.Map(d => d.ExpiryYear, s => s.ExpiryYear)
.Ignore(d => d.Cvc);
// Nested adapter for EncryptedCard
config.NewConfig<CardWithEncryptedPan, Processing.Card.V2.EncryptedCard>()
.Map(d => d.JweCompactEncryptedData, s => s.EncryptedCardData)
.Map(d => d.EncryptedDataContentType,
s => Processing.Card.V2.EncryptedCardContentType.JsonCardNumberWithOptCvvCamelCase)
.Map(d => d.ExpiryYear, s => s.ExpiryYear)
.Map(d => d.ExpiryMonth, s => s.ExpiryMonth);
// Main mapping with conditional logic
config.NewConfig<AccountUpdaterInquiryResponse, InquiryResponse>()
.Map(d => d.PlaintextCard, s => s.Card.Value.AsT0, s => s.Card.HasValue && s.Card.Value.IsT0)
.Map(d => d.EncryptedCard, s => s.Card.Value.AsT1, s => s.Card.HasValue && s.Card.Value.IsT1)
.Map(d => d.Status, s => ConvertInquiryStatus(s.Status));
}
}
Test Configuration
var config = new TypeAdapterConfig();
config.Default.RequireDestinationMemberSource(value: true);
new InquiryResponseMapper().Register(config);
config.Compile();
var mapper = new Mapper(config);
Expected Behavior
Both PlaintextCard and EncryptedCard should be mapped through their respective nested adapters:
- When
Card.Value.IsT0is true,PlaintextCardshould be populated viaCardWithPlainTextPan -> Processing.Card.V2.Cardadapter - When
Card.Value.IsT1is true,EncryptedCardshould be populated viaCardWithEncryptedPan -> Processing.Card.V2.EncryptedCardadapter
Actual Behavior
- ✅
EncryptedCardworks correctly - nested adapter is applied, all fields mapped - ❌
PlaintextCardisnull- nested adapter is NOT applied, even though the condition evaluates totrue
Test Results
Passed: 18 tests for EncryptedCard mapping
Failed: 8 tests for PlaintextCard mapping (NullReferenceException on line accessing PlaintextCard.Pan)
What We've Tried
- Reordering configurations - Registering PlaintextCard adapter before EncryptedCard
- Different mapping approaches:
- Ternary operator:
.Map(d => d.PlaintextCard, s => s.Card.HasValue && s.Card.Value.IsT0 ? s.Card.Value.AsT0 : null) - AfterMapping callbacks
- Manual adapter invocation functions
- Ternary operator:
- Property mapping variations:
- Changed property order to match constructor
- Explicit mapping of Cvc to empty string instead of Ignore
- Type disambiguation - Added
using Card = Processing.Card.V2.Card;alias - ForType declarations - Explicitly declaring both adapters with
config.ForType<>()
None of these approaches resolved the issue, while EncryptedCard continues to work perfectly with the exact same pattern.
Key Observations
- The problem is asymmetric - identical mapping patterns behave differently
- The destination type name
Cardis very generic, whileEncryptedCardis more specific - Source types use OneOf discriminated unions from the OneOf NuGet package
- Destination types are protobuf-generated with oneof fields
- The nested adapter IS registered and CAN be used directly, but isn't invoked through conditional mapping
RequireDestinationMemberSource(true)is satisfied (no compilation errors about unmapped members)
Workaround Needed
Is there a recommended pattern for mapping through OneOf types with nested adapters that ensures consistent behavior? Or is this a bug that needs fixing?
Additional Context
This appears to be related to how Mapster resolves and applies nested adapters when the source value comes from a conditional expression involving discriminated union types. The fact that one type works and another doesn't suggests there may be type resolution or caching issues specific to certain type names or structures.
- Ngôn ngữ chính
- C#
- Star
- 5.2k
- Fork
- 409
- Merge trung bình
- 3 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 3
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của MapsterMapper/Mapster
-
.NET MatrixĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
MapsterMapper/Mapster#1003 ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 58/100
MapsterMapper/Mapster#1024 · 1 bình luận ·
-
[Mapster Tool] Add support for global:: to disambiguate type names in generated mappersCó thể đã có người làm @DocSvartz đã nhận 2 ngày trước. Đang mởimprovement
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 65/100
MapsterMapper/Mapster#1020 · 5 bình luận ·
-
Mapster.Tool - PropertySettingBuilder.Map(member, mapFunc, targetPropertyName) method is not working as expected!Có thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 68/100
MapsterMapper/Mapster#1017 · 6 bình luận ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 57/100
MapsterMapper/Mapster#1007 · 5 bình luận ·
Tất cả issue của MapsterMapper/Mapster
Issue tương tự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
KnifeLemon/Filee#363 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
-
area-Native untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
dotnet/machinelearning#7837 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
[CI] .trgen-ignore required for several grammars.Có thể đã có người làm @kaby76 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
antlr/grammars-v4#5073 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Enhancement]: Add a search box to the Time Zone dropdownCó thể đã có người làm @idumlupinar đã nhận hôm nay. Đang mởStatus: New
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
dnnsoftware/Dnn.Platform#7579 ·
Maintainer thường phản hồi trong vòng 2 ngày