Search code examples
c#csvhelper

CsvHelper return null values for complex types if all properties are null


I'm mapping a complex object with child object's in a CSV file and I'm using CsvHelper 12.1.2 to read it in. The issue I'm having is that if ALL fields for a child object are null I would like the entire entity to be null.

Here's what I have:

Worker
  Name
  Age
  PhoneNumber
    AreaCode
    Prefix
    LineNumber

If AreaCode + Prefix + LineNumber is null then I want PhoneNumber to be null.

Here's a partial of the code I'm using:

public class WorkerModelMap : ClassMap<Worker>
{
    public WorkerModelMap()
    {
        Map(x => x.VoterId).Name("VoterId");
        Map(x => x.FirstName).Name("FirstName");
        Map(x => x.MiddleName).Name("MiddleName");
        Map(x => x.LastName).Name("LastName");
        Map(x => x.Suffix).Name("Suffix");
        Map(x => x.Email).Name("Email");
        Map(x => x.Party).Name("Party");
        Map(x => x.DateOfBirth).Optional().Name("DateOfBirth").ConvertUsing(row => NodaTimeExtensions.ConvertStringToLocalDate(row.GetField("DateOfBirth")));
        Map(x => x.Status).Name("Status").ConvertUsing(row =>
        {
            if (!int.TryParse(row.GetField("Status"), out int status) || !Enum.IsDefined(typeof(WorkerStatus), status))
            {
                // Unknown is the default
                return WorkerStatus.Unknown;
            }

            return (WorkerStatus)Enum.Parse(typeof(WorkerStatus), row.GetField("Status"));
        });

        Map(x => x.Type).Name("WorkerType").ConvertUsing(row =>
        {
            if (!int.TryParse(row.GetField("WorkerType"), out int type) || !Enum.IsDefined(typeof(WorkerType), type))
            {
                // Unknown is the default
                return WorkerType.Unknown;
            }

            return (WorkerType)Enum.Parse(typeof(WorkerType), row.GetField("WorkerType"));
        });

        Map(x => x.IsProtected).Name("IsProtected").ConvertUsing(row =>
        {
            if (row.GetField("IsProtected") == "Y")
            {
                return true;
            }

            return false;
        });
            
        References<HomePhoneNumberModelMap>(x => x.Phone);
        References<BusinessPhoneNumberModelMap>(x => x.BusinessTelephone);
        References<MobilePhoneNumberModelMap>(x => x.MobileTelephone);
        References<ResidentialAddressModelMap>(x => x.ResidentialAddress);
        References<PaymentAddressModelMap>(x => x.PaymentAddress);
    }
}

public class HomePhoneNumberModelMap : ClassMap<PhoneNumber>
{
    public HomePhoneNumberModelMap() 
    {
        Map(x => x.AreaCode).Name("HomeAreaCode");
        Map(x => x.Prefix).Name("HomePrefix");
        Map(x => x.LineNumber).Name("HomeLineNumber");
    }
}

I'm not sure if it's a mapper issue or if there's a configuration setting, but any help is appreciated. I know this IS possible as I had it working a few days ago but somehow lost those changes!! Thanks!


Solution

  • I found a solution. I ended up swapping "References" for "ConvertUsing":

    Map(m => m.Phone).ConvertUsing(NullPhoneNumberParser);
    
    public PhoneNumber NullPhoneNumberParser(IReaderRow row)
    {
        var rawAreaCode = row.GetField<string>(row.Context.CurrentIndex + 1);
        var rawPrefix = row.GetField<string>(row.Context.CurrentIndex + 1);
        var rawLineNumber = row.GetField<string>(row.Context.CurrentIndex + 1);
    
        if (string.IsNullOrWhiteSpace(rawAreaCode) && string.IsNullOrWhiteSpace(rawPrefix) && string.IsNullOrWhiteSpace(rawLineNumber))
        {
            return null;
        }
    
        return new PhoneNumber()
        {
            AreaCode = rawAreaCode,
            Prefix = rawPrefix,
            LineNumber = rawLineNumber,
        };
    }