sgch
sgch

Reputation: 139

CsvHelper write mapper one property to multiple columns

I'm using csvHelper (version 2.8.4) to write a class to csv. My class looks like this:

public class classA
{
   public int Amount { get; set; }
   public Dictionary<string, string> Dict{ get; set; }
}

Is it possible to write a mapper that maps Dict property to multiple columns? using some sort of converter?

for example if the class has the values:
Amount = 15
Dict = new Dictionary<string,string>{["a1"] = "a2",["b1"] = "b2"}

I want the resulting csv to be:

Amount,a1,b1
15,a2,b2

Thanks!

Upvotes: 2

Views: 1812

Answers (2)

David Specht
David Specht

Reputation: 9044

Possibly the easiest way is going to be to manually write out the dictionary part.

*** Update to work with CsvHelper Version 2.8.4 ***

void Main()
{
    var records = new List<classA>
    {
        new classA {
            Amount = 15,
            Dict = new Dictionary<string,string>{["a1"] = "a2",["b1"] = "b2"}
        }
    };
    
    using (var csv = new CsvWriter(Console.Out))
    {
        var dict = records.First().Dict;

        var properties = typeof(classA).GetProperties();
        
        foreach (PropertyInfo property in properties)
        {
            if (property.Name != "Dict")
            {
                csv.WriteField(property.Name);
            }
        }

        foreach (var item in dict)
        {
            csv.WriteField(item.Key);
        }

        csv.NextRecord();

        foreach (var record in records)
        {
            foreach (PropertyInfo property in properties)
            {
                if (property.Name != "Dict")
                {
                    csv.WriteField(property.GetValue(record));
                }
            }

            foreach (var item in record.Dict)
            {
                csv.WriteField(item.Value);
            }

            csv.NextRecord();
        }
    }
}

// You can define other methods, fields, classes and namespaces here

public class classA
{
    public int Amount { get; set; }
    public Dictionary<string, string> Dict { get; set; }
}

*** Works for current Version 27.2.1 ***

void Main()
{
    var records = new List<classA>
    {
        new classA { Amount = 15, Dict = new Dictionary<string,string>{["a1"] = "a2",["b1"] = "b2"} },
    };

    using (var csv = new CsvWriter(Console.Out, CultureInfo.InvariantCulture))
    {
        var dict = records.First().Dict;
        
        csv.WriteHeader<classA>();
        
        foreach (var item in dict)
        {
            csv.WriteField(item.Key);   
        }
        
        csv.NextRecord();
        
        foreach (var record in records)
        {
            csv.WriteRecord(record);

            foreach (var item in record.Dict)
            {
                csv.WriteField(item.Value);
            }
            
            csv.NextRecord();
        }       
    }
}

public class classA
{
    public int Amount { get; set; }
    public Dictionary<string, string> Dict { get; set; }
}

Upvotes: 2

Renat
Renat

Reputation: 8962

As mentioned in linked question, you may use ExpandoObject to serialize dictionary.

The following code will work for writing to CSV only, it's converting classA objects to ExpandoObject during serialization, including Amount property which is added manually.

public static List<dynamic> ToExpandoObjects(IReadOnlyList<classA> aObjects)
{
    var allKeys = aObjects
        .SelectMany(a => a.Dict.Keys)
        .Distinct()
        .ToHashSet();

    var result = new List<dynamic>();

    foreach (var a in aObjects)
    {
        var asExpando = new ExpandoObject();
        var asDictionary = (IDictionary<string, object>)asExpando;

        asDictionary[nameof(classA.Amount)] = a.Amount;
        foreach (var key in allKeys)
        {
            if(a.Dict.TryGetValue(key, out var value))
                asDictionary[key] = value;
            else
                asDictionary[key] = null;
        }

        result.Add(asExpando);
    }

    return result;
}

...
    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
    {
        csv.WriteRecords(ToExpandoObjects(records));
    }

E.g. called as:

var records = new[] {
    new classA
    {
        Amount = 15,
        Dict = new Dictionary<string,string>{["a1"] = "a2",["b1"] = "b2"}
    },
    new classA
    {
        Amount = 15,
        Dict = new Dictionary<string,string>{["c1"] = "c2",["b1"] = "b2"}
    }
};

StringBuilder sb = new StringBuilder();
using (var writer = new StringWriter(sb))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    csv.WriteRecords(ToExpandoObjects(records));
}

Console.WriteLine(sb.ToString());

produces

Amount a1 b1 c1
15 a2 b2
15 b2 c2

Upvotes: 2

Related Questions