CsvHelper 将映射器一 属性 写入多列

CsvHelper write mapper one property to multiple columns

我正在使用 csvHelper(版本 2.8.4)将 class 写入 csv。 我的 class 看起来像这样:

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

是否可以编写将 Dict 属性 映射到多列的映射器?使用某种转换器?

例如,如果 class 具有以下值:
数量 = 15
Dict = new Dictionary{["a1"] = "a2",["b1"] = "b2"}

我希望生成的 csv 为:

Amount,a1,b1
15,a2,b2

谢谢!

如链接问题中所述,您可以使用 ExpandoObject 来序列化字典。

以下代码仅适用于写入 CSV,它在序列化期间将 classA 对象转换为 ExpandoObject,包括手动添加的 Amount 属性。

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));
    }

例如称为:

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());

生产

Amount a1 b1 c1
15 a2 b2
15 b2 c2

可能最简单的方法是手动写出字典部分。

*** 更新以使用 CsvHelper 版本 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; }
}

*** 适用于当前版本 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; }
}