Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Conditional mapping with nested adapters fails for one type but works for another identical pattern

Aperta
#926 9 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Tranquilla
Stack tecnologico
csharp
Ambito
tooling

Direzione di ricerca

Inizia con la configurazione InquiryResponseMapper.Register fornita, quindi riproduci il mapping tramite TypeAdapterConfig.Compile() e Mapper per entrambi i rami di OneOf. Confronta il mapping condizionale di PlaintextCard ed EncryptedCard con i relativi adapter annidati registrati; il lavoro è completato quando PlaintextCard viene popolato tramite il proprio adapter senza interrompere il percorso funzionante di EncryptedCard.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

configuration problem

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.IsT0 is true, PlaintextCard should be populated via CardWithPlainTextPan -> Processing.Card.V2.Card adapter
  • When Card.Value.IsT1 is true, EncryptedCard should be populated via CardWithEncryptedPan -> Processing.Card.V2.EncryptedCard adapter

Actual Behavior

  • ✅ EncryptedCard works correctly - nested adapter is applied, all fields mapped
  • ❌ PlaintextCard is null - nested adapter is NOT applied, even though the condition evaluates to true
Test Results
Passed:  18 tests for EncryptedCard mapping
Failed:   8 tests for PlaintextCard mapping (NullReferenceException on line accessing PlaintextCard.Pan)

What We've Tried

  1. Reordering configurations - Registering PlaintextCard adapter before EncryptedCard
  2. 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
  3. Property mapping variations:
    • Changed property order to match constructor
    • Explicit mapping of Cvc to empty string instead of Ignore
  4. Type disambiguation - Added using Card = Processing.Card.V2.Card; alias
  5. 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

  1. The problem is asymmetric - identical mapping patterns behave differently
  2. The destination type name Card is very generic, while EncryptedCard is more specific
  3. Source types use OneOf discriminated unions from the OneOf NuGet package
  4. Destination types are protobuf-generated with oneof fields
  5. The nested adapter IS registered and CAN be used directly, but isn't invoked through conditional mapping
  6. 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.

Lingua principale
C#
Stelle
5.2k
Fork
411
Merge medio
3g 4h
PR unite (30g)
3

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di MapsterMapper/Mapster

Tutte le issue di MapsterMapper/Mapster

Issue simili

Altre issue su C#

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.