Parsing CSV files in C#, with header

C#CsvFile IoIoHeader

C# Problem Overview


Is there a default/official/recommended way to parse CSV files in C#? I don't want to roll my own parser.

Also, I've seen instances of people using ODBC/OLE DB to read CSV via the Text driver, and a lot of people discourage this due to its "drawbacks." What are these drawbacks?

Ideally, I'm looking for a way through which I can read the CSV by column name, using the first record as the header / field names. Some of the answers given are correct but work to basically deserialize the file into classes.

C# Solutions


Solution 1 - C#

A CSV parser is now a part of .NET Framework.

Add a reference to Microsoft.VisualBasic.dll (works fine in C#, don't mind the name)

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData)
    {
        //Process row
        string[] fields = parser.ReadFields();
        foreach (string field in fields)
        {
            //TODO: Process field
        }
    }
}

The docs are here - TextFieldParser Class

P.S. If you need a CSV exporter, try CsvExport (discl: I'm one of the contributors)

Solution 2 - C#

CsvHelper (a library I maintain) will read a CSV file into custom objects.

using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    var records = csv.GetRecords<Foo>();
}

Sometimes you don't own the objects you're trying to read into. In this case, you can use fluent mapping because you can't put attributes on the class.

public sealed class MyCustomObjectMap : CsvClassMap<MyCustomObject>
{
    public MyCustomObjectMap()
    {
        Map( m => m.Property1 ).Name( "Column Name" );
        Map( m => m.Property2 ).Index( 4 );
        Map( m => m.Property3 ).Ignore();
        Map( m => m.Property4 ).TypeConverter<MySpecialTypeConverter>();
    }
}

Solution 3 - C#

Let a library handle all the nitty-gritty details for you! :-)

Check out FileHelpers and stay DRY - Don't Repeat Yourself - no need to re-invent the wheel a gazillionth time....

You basically just need to define that shape of your data - the fields in your individual line in the CSV - by means of a public class (and so well-thought out attributes like default values, replacements for NULL values and so forth), point the FileHelpers engine at a file, and bingo - you get back all the entries from that file. One simple operation - great performance!

Solution 4 - C#

In a business application, i use the Open Source project on codeproject.com, CSVReader.

It works well, and has good performance. There is some benchmarking on the link i provided.

A simple example, copied from the project page:

using (CsvReader csv = new CsvReader(new StreamReader("data.csv"), true))
{
    int fieldCount = csv.FieldCount;
    string[] headers = csv.GetFieldHeaders();

    while (csv.ReadNextRecord())
    {
        for (int i = 0; i < fieldCount; i++)
            Console.Write(string.Format("{0} = {1};", headers[i], csv[i]));

        Console.WriteLine();
    }
}

As you can see, it's very easy to work with.

Solution 5 - C#

I know its a bit late but just found a library Microsoft.VisualBasic.FileIO which has TextFieldParser class to process csv files.

Solution 6 - C#

If you need only reading csv files then I recommend this library: A Fast CSV Reader
If you also need to generate csv files then use this one: FileHelpers

Both of them are free and opensource.

Solution 7 - C#

Here is a helper class I use often, in case any one ever comes back to this thread (I wanted to share it).

I use this for the simplicity of porting it into projects ready to use:

public class CSVHelper : List<string[]>
{
  protected string csv = string.Empty;
  protected string separator = ",";

  public CSVHelper(string csv, string separator = "\",\"")
  {
    this.csv = csv;
    this.separator = separator;

    foreach (string line in Regex.Split(csv, System.Environment.NewLine).ToList().Where(s => !string.IsNullOrEmpty(s)))
    {
      string[] values = Regex.Split(line, separator);

      for (int i = 0; i < values.Length; i++)
      {
        //Trim values
        values[i] = values[i].Trim('\"');
      }

      this.Add(values);
    }
  }
}

And use it like:

public List<Person> GetPeople(string csvContent)
{
  List<Person> people = new List<Person>();
  CSVHelper csv = new CSVHelper(csvContent);
  foreach(string[] line in csv)
  {
    Person person = new Person();
    person.Name = line[0];
    person.TelephoneNo = line[1];
    people.Add(person);
  }
  return people;
}

[Updated csv helper: bug fixed where the last new line character created a new line]

Solution 8 - C#

This solution is using the official Microsoft.VisualBasic assembly to parse CSV.

Advantages:

  • delimiter escaping
  • ignores Header
  • trim spaces
  • ignore comments

Code:

    using Microsoft.VisualBasic.FileIO;

    public static List<List<string>> ParseCSV (string csv)
    {
        List<List<string>> result = new List<List<string>>();


        // To use the TextFieldParser a reference to the Microsoft.VisualBasic assembly has to be added to the project. 
        using (TextFieldParser parser = new TextFieldParser(new StringReader(csv))) 
        {
            parser.CommentTokens = new string[] { "#" };
            parser.SetDelimiters(new string[] { ";" });
            parser.HasFieldsEnclosedInQuotes = true;

            // Skip over header line.
            //parser.ReadLine();

            while (!parser.EndOfData)
            {
                var values = new List<string>();

                var readFields = parser.ReadFields();
                if (readFields != null)
                    values.AddRange(readFields);
                result.Add(values);
            }
        }

        return result;
    }

Solution 9 - C#

I have written TinyCsvParser for .NET, which is one of the fastest .NET parsers around and highly configurable to parse almost any CSV format.

It is released under MIT License:

You can use NuGet to install it. Run the following command in the Package Manager Console.

PM> Install-Package TinyCsvParser

Usage

Imagine we have list of Persons in a CSV file persons.csv with their first name, last name and birthdate.

FirstName;LastName;BirthDate
Philipp;Wagner;1986/05/12
Max;Musterman;2014/01/02

The corresponding domain model in our system might look like this.

private class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

When using TinyCsvParser you have to define the mapping between the columns in the CSV data and the property in you domain model.

private class CsvPersonMapping : CsvMapping<Person>
{

    public CsvPersonMapping()
        : base()
    {
        MapProperty(0, x => x.FirstName);
        MapProperty(1, x => x.LastName);
        MapProperty(2, x => x.BirthDate);
    }
}

And then we can use the mapping to parse the CSV data with a CsvParser.

namespace TinyCsvParser.Test
{
    [TestFixture]
    public class TinyCsvParserTest
    {
        [Test]
        public void TinyCsvTest()
        {
            CsvParserOptions csvParserOptions = new CsvParserOptions(true, new[] { ';' });
            CsvPersonMapping csvMapper = new CsvPersonMapping();
            CsvParser<Person> csvParser = new CsvParser<Person>(csvParserOptions, csvMapper);

            var result = csvParser
                .ReadFromFile(@"persons.csv", Encoding.ASCII)
                .ToList();

            Assert.AreEqual(2, result.Count);

            Assert.IsTrue(result.All(x => x.IsValid));
			
            Assert.AreEqual("Philipp", result[0].Result.FirstName);
            Assert.AreEqual("Wagner", result[0].Result.LastName);

            Assert.AreEqual(1986, result[0].Result.BirthDate.Year);
            Assert.AreEqual(5, result[0].Result.BirthDate.Month);
            Assert.AreEqual(12, result[0].Result.BirthDate.Day);

            Assert.AreEqual("Max", result[1].Result.FirstName);
            Assert.AreEqual("Mustermann", result[1].Result.LastName);

            Assert.AreEqual(2014, result[1].Result.BirthDate.Year);
            Assert.AreEqual(1, result[1].Result.BirthDate.Month);
            Assert.AreEqual(1, result[1].Result.BirthDate.Day);
        }
    }
}

User Guide

A full User Guide is available at:

Solution 10 - C#

Here is a short and simple solution.

                using (TextFieldParser parser = new TextFieldParser(outputLocation))
                 {
                        parser.TextFieldType = FieldType.Delimited;
                        parser.SetDelimiters(",");
                        string[] headers = parser.ReadLine().Split(',');
                        foreach (string header in headers)
                        {
                            dataTable.Columns.Add(header);
                        }
                        while (!parser.EndOfData)
                        {
                            string[] fields = parser.ReadFields();
                            dataTable.Rows.Add(fields);
                        }
                    }

Solution 11 - C#

Here is my KISS implementation...

using System;
using System.Collections.Generic;
using System.Text;

class CsvParser
{
    public static List<string> Parse(string line)
    {
        const char escapeChar = '"';
        const char splitChar = ',';
        bool inEscape = false;
        bool priorEscape = false;

        List<string> result = new List<string>();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < line.Length; i++)
        {
            char c = line[i];
            switch (c)
            {
                case escapeChar:
                    if (!inEscape)
                        inEscape = true;
                    else
                    {
                        if (!priorEscape)
                        {
                            if (i + 1 < line.Length && line[i + 1] == escapeChar)
                                priorEscape = true;
                            else
                                inEscape = false;
                        }
                        else
                        {
                            sb.Append(c);
                            priorEscape = false;
                        }
                    }
                    break;
                case splitChar:
                    if (inEscape) //if in escape
                        sb.Append(c);
                    else
                    {
                        result.Add(sb.ToString());
                        sb.Length = 0;
                    }
                    break;
                default:
                    sb.Append(c);
                    break;
            }
        }

        if (sb.Length > 0)
            result.Add(sb.ToString());

        return result;
    }

}

Solution 12 - C#

Some time ago I had wrote simple class for CSV read/write based on Microsoft.VisualBasic library. Using this simple class you will be able to work with CSV like with 2 dimensions array. You can find my class by the following link: https://github.com/ukushu/DataExporter

Simple example of usage:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

For reading header only you need is to read csv.Rows[0] cells :)

Solution 13 - C#

This code reads csv to DataTable:

public static DataTable ReadCsv(string path)
{
    DataTable result = new DataTable("SomeData");
    using (TextFieldParser parser = new TextFieldParser(path))
    {
        parser.TextFieldType = FieldType.Delimited;
        parser.SetDelimiters(",");
        bool isFirstRow = true;
        //IList<string> headers = new List<string>();

        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (isFirstRow)
            {
                foreach (string field in fields)
                {
                    result.Columns.Add(new DataColumn(field, typeof(string)));
                }
                isFirstRow = false;
            }
            else
            {
                int i = 0;
                DataRow row = result.NewRow();
                foreach (string field in fields)
                {
                    row[i++] = field;
                }
                result.Rows.Add(row);
            }
        }
    }
    return result;
}

Solution 14 - C#

Another one to this list, Cinchoo ETL - an open source library to read and write multiple file formats (CSV, flat file, Xml, JSON etc)

Sample below shows how to read CSV file quickly (No POCO object required)

string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

using (var p = ChoCSVReader.LoadText(csv)
    .WithFirstLineHeader()
    )
{
    foreach (var rec in p)
    {
        Console.WriteLine($"Id: {rec.Id}");
        Console.WriteLine($"Name: {rec.Name}");
    }
}

Sample below shows how to read CSV file using POCO object

public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

static void CSVTest()
{
    string csv = @"Id, Name
1, Carl
2, Tom
3, Mark";

    using (var p = ChoCSVReader<EmployeeRec>.LoadText(csv)
        .WithFirstLineHeader()
        )
    {
        foreach (var rec in p)
        {
            Console.WriteLine($"Id: {rec.Id}");
            Console.WriteLine($"Name: {rec.Name}");
        }
    }
}

Please check out articles at CodeProject on how to use it.

Solution 15 - C#

Single source file solution for straightforward parsing needs, useful. Deals with all the nasty edge cases. Such as new line normalization and handling new lines in quoted string literals. Your welcome!

If you CSV file has a header you just read out the column names (and compute column indexes) from the first row. Simple as that.

Note that Dump is a LINQPad method, you might want to remove that if you are not using LINQPad.

void Main()
{
	var file1 = "a,b,c\r\nx,y,z";
	CSV.ParseText(file1).Dump();

	var file2 = "a,\"b\",c\r\nx,\"y,z\"";
	CSV.ParseText(file2).Dump();

	var file3 = "a,\"b\",c\r\nx,\"y\r\nz\"";
	CSV.ParseText(file3).Dump();

	var file4 = "\"\"\"\"";
	CSV.ParseText(file4).Dump();
}

static class CSV
{
	public struct Record
	{
		public readonly string[] Row;

		public string this[int index] => Row[index];

		public Record(string[] row)
		{
			Row = row;
		}
	}

	public static List<Record> ParseText(string text)
	{
		return Parse(new StringReader(text));
	}

	public static List<Record> ParseFile(string fn)
	{
		using (var reader = File.OpenText(fn))
		{
			return Parse(reader);
		}
	}

	public static List<Record> Parse(TextReader reader)
	{
		var data = new List<Record>();

		var col = new StringBuilder();
		var row = new List<string>();
		for (; ; )
		{
			var ln = reader.ReadLine();
			if (ln == null) break;
			if (Tokenize(ln, col, row))
			{
				data.Add(new Record(row.ToArray()));
				row.Clear();
			}
		}

		return data;
	}

	public static bool Tokenize(string s, StringBuilder col, List<string> row)
	{
		int i = 0;

		if (col.Length > 0)
		{
			col.AppendLine(); // continuation

			if (!TokenizeQuote(s, ref i, col, row))
			{
				return false;
			}
		}

		while (i < s.Length)
		{
			var ch = s[i];
			if (ch == ',')
			{
				row.Add(col.ToString().Trim());
				col.Length = 0;
				i++;
			}
			else if (ch == '"')
			{
				i++;
				if (!TokenizeQuote(s, ref i, col, row))
				{
					return false;
				}
			}
			else
			{
				col.Append(ch);
				i++;
			}
		}

		if (col.Length > 0)
		{
			row.Add(col.ToString().Trim());
			col.Length = 0;
		}

		return true;
	}

	public static bool TokenizeQuote(string s, ref int i, StringBuilder col, List<string> row)
	{
		while (i < s.Length)
		{
			var ch = s[i];
			if (ch == '"')
			{
				// escape sequence
				if (i + 1 < s.Length && s[i + 1] == '"')
				{
					col.Append('"');
					i++;
					i++;
					continue;
				}
				i++;
				return true;
			}
			else
			{
				col.Append(ch);
				i++;
			}
		}
		return false;
	}
}

Solution 16 - C#

Based on unlimit's post on https://stackoverflow.com/questions/17207269/how-to-properly-split-a-csv-using-c-sharp-split-function :

string[] tokens = System.Text.RegularExpressions.Regex.Split(paramString, ",");

NOTE: this doesn't handle escaped / nested commas, etc., and therefore is only suitable for certain simple CSV lists.

Solution 17 - C#

If anyone wants a snippet they can plop into their code without having to bind a library or download a package. Here is a version I wrote:

    public static string FormatCSV(List<string> parts)
    {
        string result = "";

        foreach (string s in parts)
        {
            if (result.Length > 0)
            {
                result += ",";

                if (s.Length == 0)
                    continue;
            }

            if (s.Length > 0)
            {
                result += "\"" + s.Replace("\"", "\"\"") + "\"";
            }
            else
            {
                // cannot output double quotes since its considered an escape for a quote
                result += ",";
            }
        }

        return result;
    }

    enum CSVMode
    {
        CLOSED = 0,
        OPENED_RAW = 1,
        OPENED_QUOTE = 2
    }

    public static List<string> ParseCSV(string input)
    {
        List<string> results;

        CSVMode mode;

        char[] letters;

        string content;


        mode = CSVMode.CLOSED;

        content = "";
        results = new List<string>();
        letters = input.ToCharArray();

        for (int i = 0; i < letters.Length; i++)
        {
            char letter = letters[i];
            char nextLetter = '\0';

            if (i < letters.Length - 1)
                nextLetter = letters[i + 1];

            // If its a quote character
            if (letter == '"')
            {
                // If that next letter is a quote
                if (nextLetter == '"' && mode == CSVMode.OPENED_QUOTE)
                {
                    // Then this quote is escaped and should be added to the content

                    content += letter;

                    // Skip the escape character
                    i++;
                    continue;
                }
                else
                {
                    // otherwise its not an escaped quote and is an opening or closing one
                    // Character is skipped

                    // If it was open, then close it
                    if (mode == CSVMode.OPENED_QUOTE)
                    {
                        results.Add(content);

                        // reset the content
                        content = "";

                        mode = CSVMode.CLOSED;

                        // If there is a next letter available
                        if (nextLetter != '\0')
                        {
                            // If it is a comma
                            if (nextLetter == ',')
                            {
                                i++;
                                continue;
                            }
                            else
                            {
                                throw new Exception("Expected comma. Found: " + nextLetter);
                            }
                        }
                    }
                    else if (mode == CSVMode.OPENED_RAW)
                    {
                        // If it was opened raw, then just add the quote 
                        content += letter;
                    }
                    else if (mode == CSVMode.CLOSED)
                    {
                        // Otherwise open it as a quote 

                        mode = CSVMode.OPENED_QUOTE;
                    }
                }
            }
            // If its a comma seperator
            else if (letter == ',')
            {
                // If in quote mode
                if (mode == CSVMode.OPENED_QUOTE)
                {
                    // Just read it
                    content += letter;
                }
                // If raw, then close the content
                else if (mode == CSVMode.OPENED_RAW)
                {
                    results.Add(content);

                    content = "";

                    mode = CSVMode.CLOSED;
                }
                // If it was closed, then open it raw
                else if (mode == CSVMode.CLOSED)
                {
                    mode = CSVMode.OPENED_RAW;

                    results.Add(content);

                    content = "";
                }
            }
            else
            {
                // If opened quote, just read it
                if (mode == CSVMode.OPENED_QUOTE)
                {
                    content += letter;
                }
                // If opened raw, then read it
                else if (mode == CSVMode.OPENED_RAW)
                {
                    content += letter;
                }
                // It closed, then open raw
                else if (mode == CSVMode.CLOSED)
                {
                    mode = CSVMode.OPENED_RAW;

                    content += letter;
                }
            }
        }

        // If it was still reading when the buffer finished
        if (mode != CSVMode.CLOSED)
        {
            results.Add(content);
        }

        return results;
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid PfefferView Question on Stackoverflow
Solution 1 - C#Alex from JitbitView Answer on Stackoverflow
Solution 2 - C#Josh CloseView Answer on Stackoverflow
Solution 3 - C#marc_sView Answer on Stackoverflow
Solution 4 - C#alexnView Answer on Stackoverflow
Solution 5 - C#user1131926View Answer on Stackoverflow
Solution 6 - C#GiorgiView Answer on Stackoverflow
Solution 7 - C#Base33View Answer on Stackoverflow
Solution 8 - C#Jonas_HessView Answer on Stackoverflow
Solution 9 - C#bytefishView Answer on Stackoverflow
Solution 10 - C#The HawkView Answer on Stackoverflow
Solution 11 - C#Alex BegunView Answer on Stackoverflow
Solution 12 - C#AndrewView Answer on Stackoverflow
Solution 13 - C#polina-cView Answer on Stackoverflow
Solution 14 - C#CinchooView Answer on Stackoverflow
Solution 15 - C#John LeidegrenView Answer on Stackoverflow
Solution 16 - C#radsdauView Answer on Stackoverflow
Solution 17 - C#JohnView Answer on Stackoverflow