C# – Configuring CsvHelper when the header names are different from the properties

When your CSV header names don’t match your property names exactly, CsvHelper will throw an exception. For example, if your header name is “title” and your property name is “Title”, it’ll throw an exception like: HeaderValidationException: Header with name ‘Title'[0] was not found.

If you don’t want to (or can’t) change the names to match, then you can configure CsvHelper to map headers to properties with different names. You have three options:

  • Use the [Name] attribute on properties that need it.
  • Use CsvConfiguration.PrepareHeaderForMatch when there’s a pattern to the naming differences (such as a casing difference).
  • Use a ClassMap to explicitly declare how all properties should be mapped.

I’ll show examples of doing these options.

Option 1 – Use the [Name] attribute

Using CsvHelper’s [Name] attribute is the simplest way to make a property map to a header with a different name.

For example, let’s say you have the following CSV (notice the DirName header):

Title,Year,DirName Inception,2010,Christopher Nolan Pulp Fiction,1994,Quentin Tarantino Jurassic Park,1993,Steven Spielberg
Code language: plaintext (plaintext)

Here’s how to use the [Name] attribute to map the DirName header to the Director property:

using CsvHelper.Configuration.Attributes; public class Movie { [Name("DirName")] public string Director { get; set; } public string Title { get; set; } public int Year { get; set; } }
Code language: C# (cs)

Now you can parse the CSV like usual:

using CsvHelper; using System.Globalization; using (var reader = new StreamReader(@"C:\movies.csv")) { using var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture); foreach (var movie in csvReader.GetRecords<Movie>()) { Console.WriteLine($"Movie: {movie.Title} ({movie.Year}, {movie.Director})"); } }
Code language: C# (cs)

This outputs the following:

Movie: Inception (2010, Christopher Nolan) Movie: Pulp Fiction (1994, Quentin Tarantino) Movie: Jurassic Park (1993, Steven Spielberg)
Code language: plaintext (plaintext)

Option 2 – Use PrepareHeaderForMatch

When there’s a pattern to the header and property naming difference, you can use CsvConfiguration.PrepareHeaderForMatch to programmatically transform the names so they match. This is an alternative to explicitly defining the mapping.

For example, let’s say the only difference in the naming is the casing. So we want to do case insensitive matching.

The CSV with uppercased header names:

TITLE,YEAR,DIRECTOR Inception,2010,Christopher Nolan Pulp Fiction,1994,Quentin Tarantino Jurassic Park,1993,Steven Spielberg
Code language: plaintext (plaintext)

The model class with pascal-cased property names:

public class Movie { public string Director { get; set; } public string Title { get; set; } public int Year { get; set; } }
Code language: C# (cs)

Here’s an example of how to use PrepareHeaderForMatch to make the names use the same casing so they match:

using CsvHelper; using CsvHelper.Configuration; using System.Globalization; var config = new CsvConfiguration(CultureInfo.InvariantCulture) { PrepareHeaderForMatch = (args) => { //Prepare header & prop names so they match var prepared = args.Header.ToLower(); Console.WriteLine($"Prepared for match {args.Header} => {prepared}"); return prepared; } }; using (var reader = new StreamReader(@"C:\temp\movies-option2-transform.csv")) { using var csvReader = new CsvReader(reader, config); foreach (var movie in csvReader.GetRecords<Movie>()) { Console.WriteLine($"Movie: {movie.Title} ({movie.Year}, {movie.Director})"); } }
Code language: C# (cs)

Note: Setting Context.Configuration.PrepareHeaderForMatch doesn’t work. You have to set it in the config and pass it in.

Here’s what this outputs:

Prepared for match TITLE => title Prepared for match YEAR => year Prepared for match DIRECTOR => director Prepared for match Director => director Prepared for match Title => title Prepared for match Year => year Movie: Inception (2010, Christopher Nolan) Movie: Pulp Fiction (1994, Quentin Tarantino) Movie: Jurassic Park (1993, Steven Spielberg)
Code language: plaintext (plaintext)

As you can see, when you set PrepareHeaderForMatch to a lambda, CsvHelper uses it on every header and property name and uses the returned name when doing the matching.

For your convenience, here is the concise version of the lambda (I made it verbose up above to make it clear how CsvHelper uses it):

var config = new CsvConfiguration(CultureInfo.InvariantCulture) { PrepareHeaderForMatch = (args)=> args.Header.ToLower() };
Code language: C# (cs)

Option 3 – Use a ClassMap

The final option is to declare a ClassMap and explicitly define which properties map to which headers.

Let’s say you have the following CSV:

Title,Release Year,Director Name Inception,2010,Christopher Nolan Pulp Fiction,1994,Quentin Tarantino Jurassic Park,1993,Steven Spielberg
Code language: plaintext (plaintext)

To be able to map these headers to the Title, Year, and Director respectively, create a ClassMap like this:

using CsvHelper.Configuration; public class MovieMap : ClassMap<Movie> { public MovieMap() { Map(movie => movie.Title).Name("Title"); Map(movie => movie.Year).Name("Release Year"); Map(movie => movie.Director).Name("Director Name"); } }
Code language: C# (cs)

Then register this ClassMap when parsing:

using CsvHelper; using System.Globalization; using (var reader = new StreamReader(@"C:\movies.csv")) { using var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture); csvReader.Context.RegisterClassMap<MovieMap>(); foreach (var movie in csvReader.GetRecords<Movie>()) { Console.WriteLine($"Movie: {movie.Title} ({movie.Year}, {movie.Director})"); } }
Code language: C# (cs)

This outputs the following:

Movie: Inception (2010, Christopher Nolan) Movie: Pulp Fiction (1994, Quentin Tarantino) Movie: Jurassic Park (1993, Steven Spielberg)
Code language: plaintext (plaintext)

The downside to using a ClassMap is that it’s all or nothing. You have to explicitly declare all properties you want mapped. If a property doesn’t have an explicit mapping, CsvHelper won’t populate it.

Leave a Comment