Search code examples
c#csvhelper

Export dynamic records with a different field order


I am Using csvhelper.

I have that List of dynamic objects (dynamic) that I want to write as csv but I cannot find how to specify the column order. I need the csv columns to be in a specific order. I could not use a ClassMap since it requires a type which I don't have since using dynamic. Thx

// Requirements:
// input file structure is like:  Id, TradeId, Date, Value1, value2
// need to remove TradeId, Date and write like in that order : value2, Id, value1

var fileName = @"whatever path";
var configuration = new CsvConfiguration(CultureInfo.InvariantCulture)
{
  Encoding = Encoding.UTF8, // Our file uses UTF-8 encoding.
  Delimiter = "," // The delimiter is a comma.
};

// reading input file, removing 2 columns TradeId and Date
List<dynamic> records;
using (var fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
  using (var textReader = new StreamReader(fs, Encoding.UTF8))
  using (var csv = new CsvReader(textReader, configuration))
  {
    records = csv.GetRecords<dynamic>().ToList();
    foreach (var record in records)
    {
      var dict = (IDictionary<string, object>)record;
      dict.Remove("TradeId");
      dict.Remove("Date");
    }
  }
}
// need to write value2, Id, value1

CsvConfiguration conf = new CsvConfiguration(CultureInfo.InvariantCulture);
conf.HasHeaderRecord = false; // do not write headers
using (var writer = new StreamWriter("whatever output file name"))
using (var csvw = new CsvWriter(writer, conf))
{  
  csvw.WriteRecords(records);
}






Solution

  • There was a discussion about this in 2018 and at that time @JoshClose advised regarding dynamic objects:

    It's because ExpandoObject uses a Dictionary<string, object> underneath to store the data. To give the user the ability write different types on each row, I can't do any sort of ordering based on a particular record.

    He did, however, at that time create a DynamicPropertySort configuration, which for this simple example you could use.

    CsvConfiguration conf = new CsvConfiguration(CultureInfo.InvariantCulture)
    {
        HasHeaderRecord = false,
        DynamicPropertySort = Comparer<string>.Create((x, y) =>
        {
            if (x == "Value2" || y == "Value1")
            {
                return -1;
            }
            
            return 1;           
        })
    };
    

    For anything more complex, your best bet will be to manually write out the fields.

    void Main()
    {
        var input = new StringBuilder();
        input.AppendLine("Id,TradeId,Date,Value1,Value2");
        input.AppendLine("1,11,2/28/2023,value1,value2");
        input.AppendLine("2,12,2/28/2022,value21,value22");
    
        using var reader = new StringReader(input.ToString());
        using var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture);
    
        var records = csvReader.GetRecords<dynamic>().ToList();
    
        using var csvWriter = new CsvWriter(Console.Out, CultureInfo.InvariantCulture);
    
        foreach (var record in records)
        {
            csvWriter.WriteField(record.Value2);
            csvWriter.WriteField(record.Id);
            csvWriter.WriteField(record.Value1);
            csvWriter.NextRecord();
        }
    }