Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing enumerable to csv file

Tags:

c#

csv

csvhelper

I'm sure its very straightforward but I am struggling to figure out how to write an array to file using CSVHelper.

I have a class for example

public class Test
{
public Test()
{
data = new float[]{0,1,2,3,4};
}
 public float[] data{get;set;}
}

i would like the data to be written with each array value in a separate cell. I have a custom converter below which is instead providing one cell with all the values in it.

What am I doing wrong?

public class DataArrayConverter<T> : ITypeConverter
{
    public string ConvertToString(TypeConverterOptions options, object value)
    {
        var data = (T[])value;

       var s = string.Join(",", data);

    }

    public object ConvertFromString(TypeConverterOptions options, string text)
    {
        throw new NotImplementedException();
    }

    public bool CanConvertFrom(Type type)
    {
        return type == typeof(string);
    }

    public bool CanConvertTo(Type type)
    {
        return type == typeof(string);
    }
}
like image 681
Bernard Avatar asked Mar 18 '23 17:03

Bernard


2 Answers

To further detail the answer from Josh Close, here what you need to do to write any IEnumerable (including arrays and generic lists) in a recent version (anything above 3.0) of CsvHelper!


Here the class under test:

public class Test
{
    public int[] Data { get; set; }

    public Test()
    {
        Data = new int[] { 0, 1, 2, 3, 4 };
    }
}

And a method to show how this can be saved:

static void Main()
{
    using (var writer = new StreamWriter("db.csv"))
    using (var csv = new CsvWriter(writer))
    {
        var list = new List<Test>
        {
            new Test()
        };
        csv.Configuration.HasHeaderRecord = false;
        csv.WriteRecords(list);
        writer.Flush();
    }
}

The important configuration here is csv.Configuration.HasHeaderRecord = false;. Only with this configuration you will be able to see the data in the csv file.

Further details can be found in the related unit test cases from CsvHelper.


In case you are looking for a solution to store properties of type IEnumerable with different amounts of elements, the following example might be of any help:

using CsvHelper;
using CsvHelper.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace CsvHelperSpike
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var writer = new StreamWriter("db.csv"))
            using (var csv = new CsvWriter(writer))
            {
                csv.Configuration.Delimiter = ";";
                var list = new List<AnotherTest>
                {
                    new AnotherTest("Before String") { Tags = new List<string> { "One", "Two", "Three" }, After="After String" },
                    new AnotherTest("This is still before") {After="after again", Tags=new List<string>{ "Six", "seven","eight", "nine"} }
                };
                csv.Configuration.RegisterClassMap<TestIndexMap>();
                csv.WriteRecords(list);
                writer.Flush();
            }

            using(var reader = new StreamReader("db.csv"))
            using(var csv = new CsvReader(reader))
            {
                csv.Configuration.IncludePrivateMembers = true;
                csv.Configuration.RegisterClassMap<TestIndexMap>();
                var result = csv.GetRecords<AnotherTest>().ToList();
            }
        }

        private class AnotherTest
        {
            public string Before { get; private set; }
            public string After { get; set; }
            public List<string> Tags { get; set; }

            public AnotherTest() { }

            public AnotherTest(string before)
            {
                this.Before = before;
            }
        }

        private sealed class TestIndexMap : ClassMap<AnotherTest>
        {
            public TestIndexMap()
            {
                Map(m => m.Before).Index(0);
                Map(m => m.After).Index(1);
                Map(m => m.Tags).Index(2);
            }
        }
    }
}

By using the ClassMap it is possible to enable HasHeaderRecord (the default) again. It is important to note here, that this solution will only work, if the collection with different amounts of elements is the last property. Otherwise the collection needs to have a fixed amount of elements and the ClassMap needs to be adapted accordingly.

This example also shows how to handle properties with a private set. For this to work it is important to use the csv.Configuration.IncludePrivateMembers = true; configuration and have a default constructor on your class.

like image 50
Jan Suchotzki Avatar answered Mar 23 '23 09:03

Jan Suchotzki


Unfortunately, it doesn't work like that. Since you are returning , in the converter, it will quote the field, as that is a part of a single field.

Currently the only way to accomplish what you want is to write manually, which isn't too horrible.

foreach( var test in list )
{
    foreach( var item in test.Data )
    {
        csvWriter.WriteField( item );
    }

    csvWriter.NextRecord();
}

Update

Version 3 has support for reading and writing IEnumerable properties.

like image 23
Josh Close Avatar answered Mar 23 '23 10:03

Josh Close